($INBOX_DIR/description missing)help / color / mirror / Atom feed
[PATCH 3/5] Make archiver process an auxiliary process 853+ messages / 5 participants [nested] [flat]
* [PATCH 3/5] Make archiver process an auxiliary process @ 2018-11-07 07:53 Kyotaro Horiguchi <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Kyotaro Horiguchi @ 2018-11-07 07:53 UTC (permalink / raw) This is a preliminary patch for shared-memory based stats collector. Archiver process must be a auxiliary process since it uses shared memory after stats data wes moved onto shared-memory. Make the process an auxiliary process in order to make it work. --- src/backend/bootstrap/bootstrap.c | 8 +++ src/backend/postmaster/pgarch.c | 98 +++++++++---------------------------- src/backend/postmaster/pgstat.c | 6 +++ src/backend/postmaster/postmaster.c | 35 +++++++++---- src/include/miscadmin.h | 2 + src/include/pgstat.h | 1 + src/include/postmaster/pgarch.h | 4 +- 7 files changed, 67 insertions(+), 87 deletions(-) diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c index 43627ab8f4..7872a2d9d7 100644 --- a/src/backend/bootstrap/bootstrap.c +++ b/src/backend/bootstrap/bootstrap.c @@ -329,6 +329,9 @@ AuxiliaryProcessMain(int argc, char *argv[]) case BgWriterProcess: statmsg = pgstat_get_backend_desc(B_BG_WRITER); break; + case ArchiverProcess: + statmsg = pgstat_get_backend_desc(B_ARCHIVER); + break; case CheckpointerProcess: statmsg = pgstat_get_backend_desc(B_CHECKPOINTER); break; @@ -456,6 +459,11 @@ AuxiliaryProcessMain(int argc, char *argv[]) BackgroundWriterMain(); proc_exit(1); /* should never return */ + case ArchiverProcess: + /* don't set signals, archiver has its own agenda */ + PgArchiverMain(); + proc_exit(1); /* should never return */ + case CheckpointerProcess: /* don't set signals, checkpointer has its own agenda */ CheckpointerMain(); diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c index f84f882c4c..4342ebdab4 100644 --- a/src/backend/postmaster/pgarch.c +++ b/src/backend/postmaster/pgarch.c @@ -77,7 +77,6 @@ * Local data * ---------- */ -static time_t last_pgarch_start_time; static time_t last_sigterm_time = 0; /* @@ -96,7 +95,6 @@ static volatile sig_atomic_t ready_to_stop = false; static pid_t pgarch_forkexec(void); #endif -NON_EXEC_STATIC void PgArchiverMain(int argc, char *argv[]) pg_attribute_noreturn(); static void pgarch_exit(SIGNAL_ARGS); static void ArchSigHupHandler(SIGNAL_ARGS); static void ArchSigTermHandler(SIGNAL_ARGS); @@ -114,75 +112,6 @@ static void pgarch_archiveDone(char *xlog); * ------------------------------------------------------------ */ -/* - * pgarch_start - * - * Called from postmaster at startup or after an existing archiver - * died. Attempt to fire up a fresh archiver process. - * - * Returns PID of child process, or 0 if fail. - * - * Note: if fail, we will be called again from the postmaster main loop. - */ -int -pgarch_start(void) -{ - time_t curtime; - pid_t pgArchPid; - - /* - * Do nothing if no archiver needed - */ - if (!XLogArchivingActive()) - return 0; - - /* - * Do nothing if too soon since last archiver start. This is a safety - * valve to protect against continuous respawn attempts if the archiver is - * dying immediately at launch. Note that since we will be re-called from - * the postmaster main loop, we will get another chance later. - */ - curtime = time(NULL); - if ((unsigned int) (curtime - last_pgarch_start_time) < - (unsigned int) PGARCH_RESTART_INTERVAL) - return 0; - last_pgarch_start_time = curtime; - -#ifdef EXEC_BACKEND - switch ((pgArchPid = pgarch_forkexec())) -#else - switch ((pgArchPid = fork_process())) -#endif - { - case -1: - ereport(LOG, - (errmsg("could not fork archiver: %m"))); - return 0; - -#ifndef EXEC_BACKEND - case 0: - /* in postmaster child ... */ - InitPostmasterChild(); - - /* Close the postmaster's sockets */ - ClosePostmasterPorts(false); - - /* Drop our connection to postmaster's shared memory, as well */ - dsm_detach_all(); - PGSharedMemoryDetach(); - - PgArchiverMain(0, NULL); - break; -#endif - - default: - return (int) pgArchPid; - } - - /* shouldn't get here */ - return 0; -} - /* ------------------------------------------------------------ * Local functions called by archiver follow * ------------------------------------------------------------ @@ -222,8 +151,8 @@ pgarch_forkexec(void) * The argc/argv parameters are valid only in EXEC_BACKEND case. However, * since we don't use 'em, it hardly matters... */ -NON_EXEC_STATIC void -PgArchiverMain(int argc, char *argv[]) +void +PgArchiverMain(void) { /* * Ignore all signals usually bound to some action in the postmaster, @@ -255,8 +184,27 @@ PgArchiverMain(int argc, char *argv[]) static void pgarch_exit(SIGNAL_ARGS) { - /* SIGQUIT means curl up and die ... */ - exit(1); + PG_SETMASK(&BlockSig); + + /* + * We DO NOT want to run proc_exit() callbacks -- we're here because + * shared memory may be corrupted, so we don't want to try to clean up our + * transaction. Just nail the windows shut and get out of town. Now that + * there's an atexit callback to prevent third-party code from breaking + * things by calling exit() directly, we have to reset the callbacks + * explicitly to make this work as intended. + */ + on_exit_reset(); + + /* + * Note we do exit(2) not exit(0). This is to force the postmaster into a + * system reset cycle if some idiot DBA sends a manual SIGQUIT to a random + * backend. This is necessary precisely because we don't clean up our + * shared memory state. (The "dead man switch" mechanism in pmsignal.c + * should ensure the postmaster sees this as a crash, too, but no harm in + * being doubly sure.) + */ + exit(2); } /* SIGHUP signal handler for archiver process */ diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c index b4f2b28b51..f4ec142cab 100644 --- a/src/backend/postmaster/pgstat.c +++ b/src/backend/postmaster/pgstat.c @@ -2934,6 +2934,9 @@ pgstat_bestart(void) case StartupProcess: lbeentry.st_backendType = B_STARTUP; break; + case ArchiverProcess: + beentry->st_backendType = B_ARCHIVER; + break; case BgWriterProcess: lbeentry.st_backendType = B_BG_WRITER; break; @@ -4277,6 +4280,9 @@ pgstat_get_backend_desc(BackendType backendType) switch (backendType) { + case B_ARCHIVER: + backendDesc = "archiver"; + break; case B_AUTOVAC_LAUNCHER: backendDesc = "autovacuum launcher"; break; diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index 3339804be9..0ca0b3024b 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -146,7 +146,8 @@ #define BACKEND_TYPE_AUTOVAC 0x0002 /* autovacuum worker process */ #define BACKEND_TYPE_WALSND 0x0004 /* walsender process */ #define BACKEND_TYPE_BGWORKER 0x0008 /* bgworker process */ -#define BACKEND_TYPE_ALL 0x000F /* OR of all the above */ +#define BACKEND_TYPE_ARCHIVER 0x0010 /* archiver process */ +#define BACKEND_TYPE_ALL 0x001F /* OR of all the above */ #define BACKEND_TYPE_WORKER (BACKEND_TYPE_AUTOVAC | BACKEND_TYPE_BGWORKER) @@ -539,6 +540,7 @@ static void ShmemBackendArrayRemove(Backend *bn); #endif /* EXEC_BACKEND */ #define StartupDataBase() StartChildProcess(StartupProcess) +#define StartArchiver() StartChildProcess(ArchiverProcess) #define StartBackgroundWriter() StartChildProcess(BgWriterProcess) #define StartCheckpointer() StartChildProcess(CheckpointerProcess) #define StartWalWriter() StartChildProcess(WalWriterProcess) @@ -1762,7 +1764,7 @@ ServerLoop(void) /* If we have lost the archiver, try to start a new one. */ if (PgArchPID == 0 && PgArchStartupAllowed()) - PgArchPID = pgarch_start(); + PgArchPID = StartArchiver(); /* If we need to signal the autovacuum launcher, do so now */ if (avlauncher_needs_signal) @@ -2977,7 +2979,7 @@ reaper(SIGNAL_ARGS) if (!IsBinaryUpgrade && AutoVacuumingActive() && AutoVacPID == 0) AutoVacPID = StartAutoVacLauncher(); if (PgArchStartupAllowed() && PgArchPID == 0) - PgArchPID = pgarch_start(); + PgArchPID = StartArchiver(); if (PgStatPID == 0) PgStatPID = pgstat_start(); @@ -3122,10 +3124,8 @@ reaper(SIGNAL_ARGS) { PgArchPID = 0; if (!EXIT_STATUS_0(exitstatus)) - LogChildExit(LOG, _("archiver process"), - pid, exitstatus); - if (PgArchStartupAllowed()) - PgArchPID = pgarch_start(); + HandleChildCrash(pid, exitstatus, + _("archiver process")); continue; } @@ -3371,7 +3371,7 @@ CleanupBackend(int pid, /* * HandleChildCrash -- cleanup after failed backend, bgwriter, checkpointer, - * walwriter, autovacuum, or background worker. + * walwriter, autovacuum, archiver or background worker. * * The objectives here are to clean up our local state about the child * process, and to signal all other remaining children to quickdie. @@ -3576,6 +3576,18 @@ HandleChildCrash(int pid, int exitstatus, const char *procname) signal_child(AutoVacPID, (SendStop ? SIGSTOP : SIGQUIT)); } + /* Take care of the archiver too */ + if (pid == PgArchPID) + PgArchPID = 0; + else if (PgArchPID != 0 && take_action) + { + ereport(DEBUG2, + (errmsg_internal("sending %s to process %d", + (SendStop ? "SIGSTOP" : "SIGQUIT"), + (int) PgArchPID))); + signal_child(PgArchPID, (SendStop ? SIGSTOP : SIGQUIT)); + } + /* * Force a power-cycle of the pgarch process too. (This isn't absolutely * necessary, but it seems like a good idea for robustness, and it @@ -3848,6 +3860,7 @@ PostmasterStateMachine(void) Assert(CheckpointerPID == 0); Assert(WalWriterPID == 0); Assert(AutoVacPID == 0); + Assert(PgArchPID == 0); /* syslogger is not considered here */ pmState = PM_NO_CHILDREN; } @@ -5117,7 +5130,7 @@ sigusr1_handler(SIGNAL_ARGS) */ Assert(PgArchPID == 0); if (XLogArchivingAlways()) - PgArchPID = pgarch_start(); + PgArchPID = StartArchiver(); /* * If we aren't planning to enter hot standby mode later, treat @@ -5400,6 +5413,10 @@ StartChildProcess(AuxProcType type) ereport(LOG, (errmsg("could not fork startup process: %m"))); break; + case ArchiverProcess: + ereport(LOG, + (errmsg("could not fork archiver process: %m"))); + break; case BgWriterProcess: ereport(LOG, (errmsg("could not fork background writer process: %m"))); diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index 61a24c2e3c..0b49b63327 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -399,6 +399,7 @@ typedef enum BootstrapProcess, StartupProcess, BgWriterProcess, + ArchiverProcess, CheckpointerProcess, WalWriterProcess, WalReceiverProcess, @@ -411,6 +412,7 @@ extern AuxProcType MyAuxProcType; #define AmBootstrapProcess() (MyAuxProcType == BootstrapProcess) #define AmStartupProcess() (MyAuxProcType == StartupProcess) #define AmBackgroundWriterProcess() (MyAuxProcType == BgWriterProcess) +#define AmArchiverProcess() (MyAuxProcType == ArchiverProcess) #define AmCheckpointerProcess() (MyAuxProcType == CheckpointerProcess) #define AmWalWriterProcess() (MyAuxProcType == WalWriterProcess) #define AmWalReceiverProcess() (MyAuxProcType == WalReceiverProcess) diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 0a3ad3a188..b3f00e1943 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -719,6 +719,7 @@ typedef struct PgStat_GlobalStats */ typedef enum BackendType { + B_ARCHIVER, B_AUTOVAC_LAUNCHER, B_AUTOVAC_WORKER, B_BACKEND, diff --git a/src/include/postmaster/pgarch.h b/src/include/postmaster/pgarch.h index 2474eac26a..88f16863d4 100644 --- a/src/include/postmaster/pgarch.h +++ b/src/include/postmaster/pgarch.h @@ -32,8 +32,6 @@ */ extern int pgarch_start(void); -#ifdef EXEC_BACKEND -extern void PgArchiverMain(int argc, char *argv[]) pg_attribute_noreturn(); -#endif +extern void PgArchiverMain(void) pg_attribute_noreturn(); #endif /* _PGARCH_H */ -- 2.16.3 ----Next_Part(Thu_Jul_11_16_40_59_2019_397)-- Content-Type: Text/X-Patch; charset=us-ascii Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="v21-0004-Shared-memory-based-stats-collector.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH 3/5] Make archiver process an auxiliary process @ 2018-11-07 07:53 Kyotaro Horiguchi <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Kyotaro Horiguchi @ 2018-11-07 07:53 UTC (permalink / raw) This is a preliminary patch for shared-memory based stats collector. Archiver process must be a auxiliary process since it uses shared memory after stats data wes moved onto shared-memory. Make the process an auxiliary process in order to make it work. --- src/backend/bootstrap/bootstrap.c | 8 +++ src/backend/postmaster/pgarch.c | 98 +++++++++---------------------------- src/backend/postmaster/pgstat.c | 6 +++ src/backend/postmaster/postmaster.c | 35 +++++++++---- src/include/miscadmin.h | 2 + src/include/pgstat.h | 1 + src/include/postmaster/pgarch.h | 4 +- 7 files changed, 67 insertions(+), 87 deletions(-) diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c index d8776e192e..f58652fd4f 100644 --- a/src/backend/bootstrap/bootstrap.c +++ b/src/backend/bootstrap/bootstrap.c @@ -329,6 +329,9 @@ AuxiliaryProcessMain(int argc, char *argv[]) case BgWriterProcess: statmsg = pgstat_get_backend_desc(B_BG_WRITER); break; + case ArchiverProcess: + statmsg = pgstat_get_backend_desc(B_ARCHIVER); + break; case CheckpointerProcess: statmsg = pgstat_get_backend_desc(B_CHECKPOINTER); break; @@ -456,6 +459,11 @@ AuxiliaryProcessMain(int argc, char *argv[]) BackgroundWriterMain(); proc_exit(1); /* should never return */ + case ArchiverProcess: + /* don't set signals, archiver has its own agenda */ + PgArchiverMain(); + proc_exit(1); /* should never return */ + case CheckpointerProcess: /* don't set signals, checkpointer has its own agenda */ CheckpointerMain(); diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c index f84f882c4c..4342ebdab4 100644 --- a/src/backend/postmaster/pgarch.c +++ b/src/backend/postmaster/pgarch.c @@ -77,7 +77,6 @@ * Local data * ---------- */ -static time_t last_pgarch_start_time; static time_t last_sigterm_time = 0; /* @@ -96,7 +95,6 @@ static volatile sig_atomic_t ready_to_stop = false; static pid_t pgarch_forkexec(void); #endif -NON_EXEC_STATIC void PgArchiverMain(int argc, char *argv[]) pg_attribute_noreturn(); static void pgarch_exit(SIGNAL_ARGS); static void ArchSigHupHandler(SIGNAL_ARGS); static void ArchSigTermHandler(SIGNAL_ARGS); @@ -114,75 +112,6 @@ static void pgarch_archiveDone(char *xlog); * ------------------------------------------------------------ */ -/* - * pgarch_start - * - * Called from postmaster at startup or after an existing archiver - * died. Attempt to fire up a fresh archiver process. - * - * Returns PID of child process, or 0 if fail. - * - * Note: if fail, we will be called again from the postmaster main loop. - */ -int -pgarch_start(void) -{ - time_t curtime; - pid_t pgArchPid; - - /* - * Do nothing if no archiver needed - */ - if (!XLogArchivingActive()) - return 0; - - /* - * Do nothing if too soon since last archiver start. This is a safety - * valve to protect against continuous respawn attempts if the archiver is - * dying immediately at launch. Note that since we will be re-called from - * the postmaster main loop, we will get another chance later. - */ - curtime = time(NULL); - if ((unsigned int) (curtime - last_pgarch_start_time) < - (unsigned int) PGARCH_RESTART_INTERVAL) - return 0; - last_pgarch_start_time = curtime; - -#ifdef EXEC_BACKEND - switch ((pgArchPid = pgarch_forkexec())) -#else - switch ((pgArchPid = fork_process())) -#endif - { - case -1: - ereport(LOG, - (errmsg("could not fork archiver: %m"))); - return 0; - -#ifndef EXEC_BACKEND - case 0: - /* in postmaster child ... */ - InitPostmasterChild(); - - /* Close the postmaster's sockets */ - ClosePostmasterPorts(false); - - /* Drop our connection to postmaster's shared memory, as well */ - dsm_detach_all(); - PGSharedMemoryDetach(); - - PgArchiverMain(0, NULL); - break; -#endif - - default: - return (int) pgArchPid; - } - - /* shouldn't get here */ - return 0; -} - /* ------------------------------------------------------------ * Local functions called by archiver follow * ------------------------------------------------------------ @@ -222,8 +151,8 @@ pgarch_forkexec(void) * The argc/argv parameters are valid only in EXEC_BACKEND case. However, * since we don't use 'em, it hardly matters... */ -NON_EXEC_STATIC void -PgArchiverMain(int argc, char *argv[]) +void +PgArchiverMain(void) { /* * Ignore all signals usually bound to some action in the postmaster, @@ -255,8 +184,27 @@ PgArchiverMain(int argc, char *argv[]) static void pgarch_exit(SIGNAL_ARGS) { - /* SIGQUIT means curl up and die ... */ - exit(1); + PG_SETMASK(&BlockSig); + + /* + * We DO NOT want to run proc_exit() callbacks -- we're here because + * shared memory may be corrupted, so we don't want to try to clean up our + * transaction. Just nail the windows shut and get out of town. Now that + * there's an atexit callback to prevent third-party code from breaking + * things by calling exit() directly, we have to reset the callbacks + * explicitly to make this work as intended. + */ + on_exit_reset(); + + /* + * Note we do exit(2) not exit(0). This is to force the postmaster into a + * system reset cycle if some idiot DBA sends a manual SIGQUIT to a random + * backend. This is necessary precisely because we don't clean up our + * shared memory state. (The "dead man switch" mechanism in pmsignal.c + * should ensure the postmaster sees this as a crash, too, but no harm in + * being doubly sure.) + */ + exit(2); } /* SIGHUP signal handler for archiver process */ diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c index 2a8472b91a..6c554d9736 100644 --- a/src/backend/postmaster/pgstat.c +++ b/src/backend/postmaster/pgstat.c @@ -2889,6 +2889,9 @@ pgstat_bestart(void) case StartupProcess: beentry->st_backendType = B_STARTUP; break; + case ArchiverProcess: + beentry->st_backendType = B_ARCHIVER; + break; case BgWriterProcess: beentry->st_backendType = B_BG_WRITER; break; @@ -4147,6 +4150,9 @@ pgstat_get_backend_desc(BackendType backendType) switch (backendType) { + case B_ARCHIVER: + backendDesc = "archiver"; + break; case B_AUTOVAC_LAUNCHER: backendDesc = "autovacuum launcher"; break; diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index fe599632d3..36e49b3e9e 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -146,7 +146,8 @@ #define BACKEND_TYPE_AUTOVAC 0x0002 /* autovacuum worker process */ #define BACKEND_TYPE_WALSND 0x0004 /* walsender process */ #define BACKEND_TYPE_BGWORKER 0x0008 /* bgworker process */ -#define BACKEND_TYPE_ALL 0x000F /* OR of all the above */ +#define BACKEND_TYPE_ARCHIVER 0x0010 /* archiver process */ +#define BACKEND_TYPE_ALL 0x001F /* OR of all the above */ #define BACKEND_TYPE_WORKER (BACKEND_TYPE_AUTOVAC | BACKEND_TYPE_BGWORKER) @@ -538,6 +539,7 @@ static void ShmemBackendArrayRemove(Backend *bn); #endif /* EXEC_BACKEND */ #define StartupDataBase() StartChildProcess(StartupProcess) +#define StartArchiver() StartChildProcess(ArchiverProcess) #define StartBackgroundWriter() StartChildProcess(BgWriterProcess) #define StartCheckpointer() StartChildProcess(CheckpointerProcess) #define StartWalWriter() StartChildProcess(WalWriterProcess) @@ -1761,7 +1763,7 @@ ServerLoop(void) /* If we have lost the archiver, try to start a new one. */ if (PgArchPID == 0 && PgArchStartupAllowed()) - PgArchPID = pgarch_start(); + PgArchPID = StartArchiver(); /* If we need to signal the autovacuum launcher, do so now */ if (avlauncher_needs_signal) @@ -2941,7 +2943,7 @@ reaper(SIGNAL_ARGS) if (!IsBinaryUpgrade && AutoVacuumingActive() && AutoVacPID == 0) AutoVacPID = StartAutoVacLauncher(); if (PgArchStartupAllowed() && PgArchPID == 0) - PgArchPID = pgarch_start(); + PgArchPID = StartArchiver(); if (PgStatPID == 0) PgStatPID = pgstat_start(); @@ -3086,10 +3088,8 @@ reaper(SIGNAL_ARGS) { PgArchPID = 0; if (!EXIT_STATUS_0(exitstatus)) - LogChildExit(LOG, _("archiver process"), - pid, exitstatus); - if (PgArchStartupAllowed()) - PgArchPID = pgarch_start(); + HandleChildCrash(pid, exitstatus, + _("archiver process")); continue; } @@ -3335,7 +3335,7 @@ CleanupBackend(int pid, /* * HandleChildCrash -- cleanup after failed backend, bgwriter, checkpointer, - * walwriter, autovacuum, or background worker. + * walwriter, autovacuum, archiver or background worker. * * The objectives here are to clean up our local state about the child * process, and to signal all other remaining children to quickdie. @@ -3540,6 +3540,18 @@ HandleChildCrash(int pid, int exitstatus, const char *procname) signal_child(AutoVacPID, (SendStop ? SIGSTOP : SIGQUIT)); } + /* Take care of the archiver too */ + if (pid == PgArchPID) + PgArchPID = 0; + else if (PgArchPID != 0 && take_action) + { + ereport(DEBUG2, + (errmsg_internal("sending %s to process %d", + (SendStop ? "SIGSTOP" : "SIGQUIT"), + (int) PgArchPID))); + signal_child(PgArchPID, (SendStop ? SIGSTOP : SIGQUIT)); + } + /* * Force a power-cycle of the pgarch process too. (This isn't absolutely * necessary, but it seems like a good idea for robustness, and it @@ -3816,6 +3828,7 @@ PostmasterStateMachine(void) Assert(CheckpointerPID == 0); Assert(WalWriterPID == 0); Assert(AutoVacPID == 0); + Assert(PgArchPID == 0); /* syslogger is not considered here */ pmState = PM_NO_CHILDREN; } @@ -5085,7 +5098,7 @@ sigusr1_handler(SIGNAL_ARGS) */ Assert(PgArchPID == 0); if (XLogArchivingAlways()) - PgArchPID = pgarch_start(); + PgArchPID = StartArchiver(); /* * If we aren't planning to enter hot standby mode later, treat @@ -5359,6 +5372,10 @@ StartChildProcess(AuxProcType type) ereport(LOG, (errmsg("could not fork startup process: %m"))); break; + case ArchiverProcess: + ereport(LOG, + (errmsg("could not fork archiver process: %m"))); + break; case BgWriterProcess: ereport(LOG, (errmsg("could not fork background writer process: %m"))); diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index b677c7e821..c33abee7aa 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -399,6 +399,7 @@ typedef enum BootstrapProcess, StartupProcess, BgWriterProcess, + ArchiverProcess, CheckpointerProcess, WalWriterProcess, WalReceiverProcess, @@ -411,6 +412,7 @@ extern AuxProcType MyAuxProcType; #define AmBootstrapProcess() (MyAuxProcType == BootstrapProcess) #define AmStartupProcess() (MyAuxProcType == StartupProcess) #define AmBackgroundWriterProcess() (MyAuxProcType == BgWriterProcess) +#define AmArchiverProcess() (MyAuxProcType == ArchiverProcess) #define AmCheckpointerProcess() (MyAuxProcType == CheckpointerProcess) #define AmWalWriterProcess() (MyAuxProcType == WalWriterProcess) #define AmWalReceiverProcess() (MyAuxProcType == WalReceiverProcess) diff --git a/src/include/pgstat.h b/src/include/pgstat.h index c080fa6388..e99cd4a72e 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -715,6 +715,7 @@ typedef struct PgStat_GlobalStats */ typedef enum BackendType { + B_ARCHIVER, B_AUTOVAC_LAUNCHER, B_AUTOVAC_WORKER, B_BACKEND, diff --git a/src/include/postmaster/pgarch.h b/src/include/postmaster/pgarch.h index 2474eac26a..88f16863d4 100644 --- a/src/include/postmaster/pgarch.h +++ b/src/include/postmaster/pgarch.h @@ -32,8 +32,6 @@ */ extern int pgarch_start(void); -#ifdef EXEC_BACKEND -extern void PgArchiverMain(int argc, char *argv[]) pg_attribute_noreturn(); -#endif +extern void PgArchiverMain(void) pg_attribute_noreturn(); #endif /* _PGARCH_H */ -- 2.16.3 ----Next_Part(Wed_Mar_27_16_36_25_2019_836)-- Content-Type: Text/X-Patch; charset=us-ascii Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="v18-0004-Shared-memory-based-stats-collector.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH 3/5] Make archiver process an auxiliary process @ 2018-11-07 07:53 Kyotaro Horiguchi <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Kyotaro Horiguchi @ 2018-11-07 07:53 UTC (permalink / raw) This is a preliminary patch for shared-memory based stats collector. Archiver process must be a auxiliary process since it uses shared memory after stats data wes moved onto shared-memory. Make the process an auxiliary process in order to make it work. --- src/backend/bootstrap/bootstrap.c | 8 +++ src/backend/postmaster/pgarch.c | 98 +++++++++---------------------------- src/backend/postmaster/pgstat.c | 6 +++ src/backend/postmaster/postmaster.c | 35 +++++++++---- src/include/miscadmin.h | 2 + src/include/pgstat.h | 1 + src/include/postmaster/pgarch.h | 4 +- 7 files changed, 67 insertions(+), 87 deletions(-) diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c index 43627ab8f4..7872a2d9d7 100644 --- a/src/backend/bootstrap/bootstrap.c +++ b/src/backend/bootstrap/bootstrap.c @@ -329,6 +329,9 @@ AuxiliaryProcessMain(int argc, char *argv[]) case BgWriterProcess: statmsg = pgstat_get_backend_desc(B_BG_WRITER); break; + case ArchiverProcess: + statmsg = pgstat_get_backend_desc(B_ARCHIVER); + break; case CheckpointerProcess: statmsg = pgstat_get_backend_desc(B_CHECKPOINTER); break; @@ -456,6 +459,11 @@ AuxiliaryProcessMain(int argc, char *argv[]) BackgroundWriterMain(); proc_exit(1); /* should never return */ + case ArchiverProcess: + /* don't set signals, archiver has its own agenda */ + PgArchiverMain(); + proc_exit(1); /* should never return */ + case CheckpointerProcess: /* don't set signals, checkpointer has its own agenda */ CheckpointerMain(); diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c index f84f882c4c..4342ebdab4 100644 --- a/src/backend/postmaster/pgarch.c +++ b/src/backend/postmaster/pgarch.c @@ -77,7 +77,6 @@ * Local data * ---------- */ -static time_t last_pgarch_start_time; static time_t last_sigterm_time = 0; /* @@ -96,7 +95,6 @@ static volatile sig_atomic_t ready_to_stop = false; static pid_t pgarch_forkexec(void); #endif -NON_EXEC_STATIC void PgArchiverMain(int argc, char *argv[]) pg_attribute_noreturn(); static void pgarch_exit(SIGNAL_ARGS); static void ArchSigHupHandler(SIGNAL_ARGS); static void ArchSigTermHandler(SIGNAL_ARGS); @@ -114,75 +112,6 @@ static void pgarch_archiveDone(char *xlog); * ------------------------------------------------------------ */ -/* - * pgarch_start - * - * Called from postmaster at startup or after an existing archiver - * died. Attempt to fire up a fresh archiver process. - * - * Returns PID of child process, or 0 if fail. - * - * Note: if fail, we will be called again from the postmaster main loop. - */ -int -pgarch_start(void) -{ - time_t curtime; - pid_t pgArchPid; - - /* - * Do nothing if no archiver needed - */ - if (!XLogArchivingActive()) - return 0; - - /* - * Do nothing if too soon since last archiver start. This is a safety - * valve to protect against continuous respawn attempts if the archiver is - * dying immediately at launch. Note that since we will be re-called from - * the postmaster main loop, we will get another chance later. - */ - curtime = time(NULL); - if ((unsigned int) (curtime - last_pgarch_start_time) < - (unsigned int) PGARCH_RESTART_INTERVAL) - return 0; - last_pgarch_start_time = curtime; - -#ifdef EXEC_BACKEND - switch ((pgArchPid = pgarch_forkexec())) -#else - switch ((pgArchPid = fork_process())) -#endif - { - case -1: - ereport(LOG, - (errmsg("could not fork archiver: %m"))); - return 0; - -#ifndef EXEC_BACKEND - case 0: - /* in postmaster child ... */ - InitPostmasterChild(); - - /* Close the postmaster's sockets */ - ClosePostmasterPorts(false); - - /* Drop our connection to postmaster's shared memory, as well */ - dsm_detach_all(); - PGSharedMemoryDetach(); - - PgArchiverMain(0, NULL); - break; -#endif - - default: - return (int) pgArchPid; - } - - /* shouldn't get here */ - return 0; -} - /* ------------------------------------------------------------ * Local functions called by archiver follow * ------------------------------------------------------------ @@ -222,8 +151,8 @@ pgarch_forkexec(void) * The argc/argv parameters are valid only in EXEC_BACKEND case. However, * since we don't use 'em, it hardly matters... */ -NON_EXEC_STATIC void -PgArchiverMain(int argc, char *argv[]) +void +PgArchiverMain(void) { /* * Ignore all signals usually bound to some action in the postmaster, @@ -255,8 +184,27 @@ PgArchiverMain(int argc, char *argv[]) static void pgarch_exit(SIGNAL_ARGS) { - /* SIGQUIT means curl up and die ... */ - exit(1); + PG_SETMASK(&BlockSig); + + /* + * We DO NOT want to run proc_exit() callbacks -- we're here because + * shared memory may be corrupted, so we don't want to try to clean up our + * transaction. Just nail the windows shut and get out of town. Now that + * there's an atexit callback to prevent third-party code from breaking + * things by calling exit() directly, we have to reset the callbacks + * explicitly to make this work as intended. + */ + on_exit_reset(); + + /* + * Note we do exit(2) not exit(0). This is to force the postmaster into a + * system reset cycle if some idiot DBA sends a manual SIGQUIT to a random + * backend. This is necessary precisely because we don't clean up our + * shared memory state. (The "dead man switch" mechanism in pmsignal.c + * should ensure the postmaster sees this as a crash, too, but no harm in + * being doubly sure.) + */ + exit(2); } /* SIGHUP signal handler for archiver process */ diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c index 285def556b..ccb1d0b62e 100644 --- a/src/backend/postmaster/pgstat.c +++ b/src/backend/postmaster/pgstat.c @@ -2934,6 +2934,9 @@ pgstat_bestart(void) case StartupProcess: lbeentry.st_backendType = B_STARTUP; break; + case ArchiverProcess: + beentry->st_backendType = B_ARCHIVER; + break; case BgWriterProcess: lbeentry.st_backendType = B_BG_WRITER; break; @@ -4277,6 +4280,9 @@ pgstat_get_backend_desc(BackendType backendType) switch (backendType) { + case B_ARCHIVER: + backendDesc = "archiver"; + break; case B_AUTOVAC_LAUNCHER: backendDesc = "autovacuum launcher"; break; diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index 34315b8d1a..ec9a7ca311 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -146,7 +146,8 @@ #define BACKEND_TYPE_AUTOVAC 0x0002 /* autovacuum worker process */ #define BACKEND_TYPE_WALSND 0x0004 /* walsender process */ #define BACKEND_TYPE_BGWORKER 0x0008 /* bgworker process */ -#define BACKEND_TYPE_ALL 0x000F /* OR of all the above */ +#define BACKEND_TYPE_ARCHIVER 0x0010 /* archiver process */ +#define BACKEND_TYPE_ALL 0x001F /* OR of all the above */ #define BACKEND_TYPE_WORKER (BACKEND_TYPE_AUTOVAC | BACKEND_TYPE_BGWORKER) @@ -539,6 +540,7 @@ static void ShmemBackendArrayRemove(Backend *bn); #endif /* EXEC_BACKEND */ #define StartupDataBase() StartChildProcess(StartupProcess) +#define StartArchiver() StartChildProcess(ArchiverProcess) #define StartBackgroundWriter() StartChildProcess(BgWriterProcess) #define StartCheckpointer() StartChildProcess(CheckpointerProcess) #define StartWalWriter() StartChildProcess(WalWriterProcess) @@ -1762,7 +1764,7 @@ ServerLoop(void) /* If we have lost the archiver, try to start a new one. */ if (PgArchPID == 0 && PgArchStartupAllowed()) - PgArchPID = pgarch_start(); + PgArchPID = StartArchiver(); /* If we need to signal the autovacuum launcher, do so now */ if (avlauncher_needs_signal) @@ -2977,7 +2979,7 @@ reaper(SIGNAL_ARGS) if (!IsBinaryUpgrade && AutoVacuumingActive() && AutoVacPID == 0) AutoVacPID = StartAutoVacLauncher(); if (PgArchStartupAllowed() && PgArchPID == 0) - PgArchPID = pgarch_start(); + PgArchPID = StartArchiver(); if (PgStatPID == 0) PgStatPID = pgstat_start(); @@ -3122,10 +3124,8 @@ reaper(SIGNAL_ARGS) { PgArchPID = 0; if (!EXIT_STATUS_0(exitstatus)) - LogChildExit(LOG, _("archiver process"), - pid, exitstatus); - if (PgArchStartupAllowed()) - PgArchPID = pgarch_start(); + HandleChildCrash(pid, exitstatus, + _("archiver process")); continue; } @@ -3371,7 +3371,7 @@ CleanupBackend(int pid, /* * HandleChildCrash -- cleanup after failed backend, bgwriter, checkpointer, - * walwriter, autovacuum, or background worker. + * walwriter, autovacuum, archiver or background worker. * * The objectives here are to clean up our local state about the child * process, and to signal all other remaining children to quickdie. @@ -3576,6 +3576,18 @@ HandleChildCrash(int pid, int exitstatus, const char *procname) signal_child(AutoVacPID, (SendStop ? SIGSTOP : SIGQUIT)); } + /* Take care of the archiver too */ + if (pid == PgArchPID) + PgArchPID = 0; + else if (PgArchPID != 0 && take_action) + { + ereport(DEBUG2, + (errmsg_internal("sending %s to process %d", + (SendStop ? "SIGSTOP" : "SIGQUIT"), + (int) PgArchPID))); + signal_child(PgArchPID, (SendStop ? SIGSTOP : SIGQUIT)); + } + /* * Force a power-cycle of the pgarch process too. (This isn't absolutely * necessary, but it seems like a good idea for robustness, and it @@ -3848,6 +3860,7 @@ PostmasterStateMachine(void) Assert(CheckpointerPID == 0); Assert(WalWriterPID == 0); Assert(AutoVacPID == 0); + Assert(PgArchPID == 0); /* syslogger is not considered here */ pmState = PM_NO_CHILDREN; } @@ -5117,7 +5130,7 @@ sigusr1_handler(SIGNAL_ARGS) */ Assert(PgArchPID == 0); if (XLogArchivingAlways()) - PgArchPID = pgarch_start(); + PgArchPID = StartArchiver(); /* * If we aren't planning to enter hot standby mode later, treat @@ -5400,6 +5413,10 @@ StartChildProcess(AuxProcType type) ereport(LOG, (errmsg("could not fork startup process: %m"))); break; + case ArchiverProcess: + ereport(LOG, + (errmsg("could not fork archiver process: %m"))); + break; case BgWriterProcess: ereport(LOG, (errmsg("could not fork background writer process: %m"))); diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index b677c7e821..c33abee7aa 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -399,6 +399,7 @@ typedef enum BootstrapProcess, StartupProcess, BgWriterProcess, + ArchiverProcess, CheckpointerProcess, WalWriterProcess, WalReceiverProcess, @@ -411,6 +412,7 @@ extern AuxProcType MyAuxProcType; #define AmBootstrapProcess() (MyAuxProcType == BootstrapProcess) #define AmStartupProcess() (MyAuxProcType == StartupProcess) #define AmBackgroundWriterProcess() (MyAuxProcType == BgWriterProcess) +#define AmArchiverProcess() (MyAuxProcType == ArchiverProcess) #define AmCheckpointerProcess() (MyAuxProcType == CheckpointerProcess) #define AmWalWriterProcess() (MyAuxProcType == WalWriterProcess) #define AmWalReceiverProcess() (MyAuxProcType == WalReceiverProcess) diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 929987190c..f3d4cb5637 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -719,6 +719,7 @@ typedef struct PgStat_GlobalStats */ typedef enum BackendType { + B_ARCHIVER, B_AUTOVAC_LAUNCHER, B_AUTOVAC_WORKER, B_BACKEND, diff --git a/src/include/postmaster/pgarch.h b/src/include/postmaster/pgarch.h index 2474eac26a..88f16863d4 100644 --- a/src/include/postmaster/pgarch.h +++ b/src/include/postmaster/pgarch.h @@ -32,8 +32,6 @@ */ extern int pgarch_start(void); -#ifdef EXEC_BACKEND -extern void PgArchiverMain(int argc, char *argv[]) pg_attribute_noreturn(); -#endif +extern void PgArchiverMain(void) pg_attribute_noreturn(); #endif /* _PGARCH_H */ -- 2.16.3 ----Next_Part(Fri_May_17_15_47_20_2019_554)-- Content-Type: Text/X-Patch; charset=us-ascii Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="v20-0004-Shared-memory-based-stats-collector.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH 3/5] Make archiver process an auxiliary process @ 2018-11-07 07:53 Kyotaro Horiguchi <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Kyotaro Horiguchi @ 2018-11-07 07:53 UTC (permalink / raw) This is a preliminary patch for shared-memory based stats collector. Archiver process must be a auxiliary process since it uses shared memory after stats data wes moved onto shared-memory. Make the process an auxiliary process in order to make it work. --- src/backend/bootstrap/bootstrap.c | 8 +++ src/backend/postmaster/pgarch.c | 98 +++++++++---------------------------- src/backend/postmaster/pgstat.c | 6 +++ src/backend/postmaster/postmaster.c | 35 +++++++++---- src/include/miscadmin.h | 2 + src/include/pgstat.h | 1 + src/include/postmaster/pgarch.h | 4 +- 7 files changed, 67 insertions(+), 87 deletions(-) diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c index 4d7ed8ad1a..a6c3338d40 100644 --- a/src/backend/bootstrap/bootstrap.c +++ b/src/backend/bootstrap/bootstrap.c @@ -328,6 +328,9 @@ AuxiliaryProcessMain(int argc, char *argv[]) case BgWriterProcess: statmsg = pgstat_get_backend_desc(B_BG_WRITER); break; + case ArchiverProcess: + statmsg = pgstat_get_backend_desc(B_ARCHIVER); + break; case CheckpointerProcess: statmsg = pgstat_get_backend_desc(B_CHECKPOINTER); break; @@ -455,6 +458,11 @@ AuxiliaryProcessMain(int argc, char *argv[]) BackgroundWriterMain(); proc_exit(1); /* should never return */ + case ArchiverProcess: + /* don't set signals, bgwriter has its own agenda */ + PgArchiverMain(); + proc_exit(1); /* should never return */ + case CheckpointerProcess: /* don't set signals, checkpointer has its own agenda */ CheckpointerMain(); diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c index f84f882c4c..4342ebdab4 100644 --- a/src/backend/postmaster/pgarch.c +++ b/src/backend/postmaster/pgarch.c @@ -77,7 +77,6 @@ * Local data * ---------- */ -static time_t last_pgarch_start_time; static time_t last_sigterm_time = 0; /* @@ -96,7 +95,6 @@ static volatile sig_atomic_t ready_to_stop = false; static pid_t pgarch_forkexec(void); #endif -NON_EXEC_STATIC void PgArchiverMain(int argc, char *argv[]) pg_attribute_noreturn(); static void pgarch_exit(SIGNAL_ARGS); static void ArchSigHupHandler(SIGNAL_ARGS); static void ArchSigTermHandler(SIGNAL_ARGS); @@ -114,75 +112,6 @@ static void pgarch_archiveDone(char *xlog); * ------------------------------------------------------------ */ -/* - * pgarch_start - * - * Called from postmaster at startup or after an existing archiver - * died. Attempt to fire up a fresh archiver process. - * - * Returns PID of child process, or 0 if fail. - * - * Note: if fail, we will be called again from the postmaster main loop. - */ -int -pgarch_start(void) -{ - time_t curtime; - pid_t pgArchPid; - - /* - * Do nothing if no archiver needed - */ - if (!XLogArchivingActive()) - return 0; - - /* - * Do nothing if too soon since last archiver start. This is a safety - * valve to protect against continuous respawn attempts if the archiver is - * dying immediately at launch. Note that since we will be re-called from - * the postmaster main loop, we will get another chance later. - */ - curtime = time(NULL); - if ((unsigned int) (curtime - last_pgarch_start_time) < - (unsigned int) PGARCH_RESTART_INTERVAL) - return 0; - last_pgarch_start_time = curtime; - -#ifdef EXEC_BACKEND - switch ((pgArchPid = pgarch_forkexec())) -#else - switch ((pgArchPid = fork_process())) -#endif - { - case -1: - ereport(LOG, - (errmsg("could not fork archiver: %m"))); - return 0; - -#ifndef EXEC_BACKEND - case 0: - /* in postmaster child ... */ - InitPostmasterChild(); - - /* Close the postmaster's sockets */ - ClosePostmasterPorts(false); - - /* Drop our connection to postmaster's shared memory, as well */ - dsm_detach_all(); - PGSharedMemoryDetach(); - - PgArchiverMain(0, NULL); - break; -#endif - - default: - return (int) pgArchPid; - } - - /* shouldn't get here */ - return 0; -} - /* ------------------------------------------------------------ * Local functions called by archiver follow * ------------------------------------------------------------ @@ -222,8 +151,8 @@ pgarch_forkexec(void) * The argc/argv parameters are valid only in EXEC_BACKEND case. However, * since we don't use 'em, it hardly matters... */ -NON_EXEC_STATIC void -PgArchiverMain(int argc, char *argv[]) +void +PgArchiverMain(void) { /* * Ignore all signals usually bound to some action in the postmaster, @@ -255,8 +184,27 @@ PgArchiverMain(int argc, char *argv[]) static void pgarch_exit(SIGNAL_ARGS) { - /* SIGQUIT means curl up and die ... */ - exit(1); + PG_SETMASK(&BlockSig); + + /* + * We DO NOT want to run proc_exit() callbacks -- we're here because + * shared memory may be corrupted, so we don't want to try to clean up our + * transaction. Just nail the windows shut and get out of town. Now that + * there's an atexit callback to prevent third-party code from breaking + * things by calling exit() directly, we have to reset the callbacks + * explicitly to make this work as intended. + */ + on_exit_reset(); + + /* + * Note we do exit(2) not exit(0). This is to force the postmaster into a + * system reset cycle if some idiot DBA sends a manual SIGQUIT to a random + * backend. This is necessary precisely because we don't clean up our + * shared memory state. (The "dead man switch" mechanism in pmsignal.c + * should ensure the postmaster sees this as a crash, too, but no harm in + * being doubly sure.) + */ + exit(2); } /* SIGHUP signal handler for archiver process */ diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c index 81c6499251..9e6bce8f6a 100644 --- a/src/backend/postmaster/pgstat.c +++ b/src/backend/postmaster/pgstat.c @@ -2856,6 +2856,9 @@ pgstat_bestart(void) case BgWriterProcess: beentry->st_backendType = B_BG_WRITER; break; + case ArchiverProcess: + beentry->st_backendType = B_ARCHIVER; + break; case CheckpointerProcess: beentry->st_backendType = B_CHECKPOINTER; break; @@ -4120,6 +4123,9 @@ pgstat_get_backend_desc(BackendType backendType) case B_BG_WRITER: backendDesc = "background writer"; break; + case B_ARCHIVER: + backendDesc = "archiver"; + break; case B_CHECKPOINTER: backendDesc = "checkpointer"; break; diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index ccea231e98..a663a62fd5 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -146,7 +146,8 @@ #define BACKEND_TYPE_AUTOVAC 0x0002 /* autovacuum worker process */ #define BACKEND_TYPE_WALSND 0x0004 /* walsender process */ #define BACKEND_TYPE_BGWORKER 0x0008 /* bgworker process */ -#define BACKEND_TYPE_ALL 0x000F /* OR of all the above */ +#define BACKEND_TYPE_ARCHIVER 0x0010 /* archiver process */ +#define BACKEND_TYPE_ALL 0x001F /* OR of all the above */ #define BACKEND_TYPE_WORKER (BACKEND_TYPE_AUTOVAC | BACKEND_TYPE_BGWORKER) @@ -539,6 +540,7 @@ static void ShmemBackendArrayRemove(Backend *bn); #define StartupDataBase() StartChildProcess(StartupProcess) #define StartBackgroundWriter() StartChildProcess(BgWriterProcess) +#define StartArchiver() StartChildProcess(ArchiverProcess) #define StartCheckpointer() StartChildProcess(CheckpointerProcess) #define StartWalWriter() StartChildProcess(WalWriterProcess) #define StartWalReceiver() StartChildProcess(WalReceiverProcess) @@ -1761,7 +1763,7 @@ ServerLoop(void) /* If we have lost the archiver, try to start a new one. */ if (PgArchPID == 0 && PgArchStartupAllowed()) - PgArchPID = pgarch_start(); + PgArchPID = StartArchiver(); /* If we need to signal the autovacuum launcher, do so now */ if (avlauncher_needs_signal) @@ -2924,7 +2926,7 @@ reaper(SIGNAL_ARGS) if (!IsBinaryUpgrade && AutoVacuumingActive() && AutoVacPID == 0) AutoVacPID = StartAutoVacLauncher(); if (PgArchStartupAllowed() && PgArchPID == 0) - PgArchPID = pgarch_start(); + PgArchPID = StartArchiver(); if (PgStatPID == 0) PgStatPID = pgstat_start(); @@ -3069,10 +3071,8 @@ reaper(SIGNAL_ARGS) { PgArchPID = 0; if (!EXIT_STATUS_0(exitstatus)) - LogChildExit(LOG, _("archiver process"), - pid, exitstatus); - if (PgArchStartupAllowed()) - PgArchPID = pgarch_start(); + HandleChildCrash(pid, exitstatus, + _("archiver process")); continue; } @@ -3318,7 +3318,7 @@ CleanupBackend(int pid, /* * HandleChildCrash -- cleanup after failed backend, bgwriter, checkpointer, - * walwriter, autovacuum, or background worker. + * walwriter, autovacuum, archiver or background worker. * * The objectives here are to clean up our local state about the child * process, and to signal all other remaining children to quickdie. @@ -3523,6 +3523,18 @@ HandleChildCrash(int pid, int exitstatus, const char *procname) signal_child(AutoVacPID, (SendStop ? SIGSTOP : SIGQUIT)); } + /* Take care of the archiver too */ + if (pid == PgArchPID) + PgArchPID = 0; + else if (PgArchPID != 0 && take_action) + { + ereport(DEBUG2, + (errmsg_internal("sending %s to process %d", + (SendStop ? "SIGSTOP" : "SIGQUIT"), + (int) PgArchPID))); + signal_child(PgArchPID, (SendStop ? SIGSTOP : SIGQUIT)); + } + /* * Force a power-cycle of the pgarch process too. (This isn't absolutely * necessary, but it seems like a good idea for robustness, and it @@ -3799,6 +3811,7 @@ PostmasterStateMachine(void) Assert(CheckpointerPID == 0); Assert(WalWriterPID == 0); Assert(AutoVacPID == 0); + Assert(PgArchPID == 0); /* syslogger is not considered here */ pmState = PM_NO_CHILDREN; } @@ -5068,7 +5081,7 @@ sigusr1_handler(SIGNAL_ARGS) */ Assert(PgArchPID == 0); if (XLogArchivingAlways()) - PgArchPID = pgarch_start(); + PgArchPID = StartArchiver(); /* * If we aren't planning to enter hot standby mode later, treat @@ -5346,6 +5359,10 @@ StartChildProcess(AuxProcType type) ereport(LOG, (errmsg("could not fork background writer process: %m"))); break; + case ArchiverProcess: + ereport(LOG, + (errmsg("could not fork archiver process: %m"))); + break; case CheckpointerProcess: ereport(LOG, (errmsg("could not fork checkpointer process: %m"))); diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index c9e35003a5..63a7653457 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -399,6 +399,7 @@ typedef enum BootstrapProcess, StartupProcess, BgWriterProcess, + ArchiverProcess, CheckpointerProcess, WalWriterProcess, WalReceiverProcess, @@ -411,6 +412,7 @@ extern AuxProcType MyAuxProcType; #define AmBootstrapProcess() (MyAuxProcType == BootstrapProcess) #define AmStartupProcess() (MyAuxProcType == StartupProcess) #define AmBackgroundWriterProcess() (MyAuxProcType == BgWriterProcess) +#define AmArchiverProcess() (MyAuxProcType == ArchiverProcess) #define AmCheckpointerProcess() (MyAuxProcType == CheckpointerProcess) #define AmWalWriterProcess() (MyAuxProcType == WalWriterProcess) #define AmWalReceiverProcess() (MyAuxProcType == WalReceiverProcess) diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 88a75fb798..471877d2df 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -706,6 +706,7 @@ typedef enum BackendType B_BACKEND, B_BG_WORKER, B_BG_WRITER, + B_ARCHIVER, B_CHECKPOINTER, B_STARTUP, B_WAL_RECEIVER, diff --git a/src/include/postmaster/pgarch.h b/src/include/postmaster/pgarch.h index 2474eac26a..88f16863d4 100644 --- a/src/include/postmaster/pgarch.h +++ b/src/include/postmaster/pgarch.h @@ -32,8 +32,6 @@ */ extern int pgarch_start(void); -#ifdef EXEC_BACKEND -extern void PgArchiverMain(int argc, char *argv[]) pg_attribute_noreturn(); -#endif +extern void PgArchiverMain(void) pg_attribute_noreturn(); #endif /* _PGARCH_H */ -- 2.16.3 ----Next_Part(Mon_Feb_18_21_35_31_2019_949)-- Content-Type: Text/X-Patch; charset=us-ascii Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="v14-0004-Shared-memory-based-stats-collector.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH 3/6] Make archiver process an auxiliary process @ 2018-11-07 07:53 Kyotaro Horiguchi <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Kyotaro Horiguchi @ 2018-11-07 07:53 UTC (permalink / raw) This is a preliminary patch for shared-memory based stats collector. Archiver process must be a auxiliary process since it uses shared memory after stats data wes moved onto shared-memory. Make the process an auxiliary process in order to make it work. --- src/backend/bootstrap/bootstrap.c | 8 +++ src/backend/postmaster/pgarch.c | 98 +++++++++---------------------------- src/backend/postmaster/pgstat.c | 6 +++ src/backend/postmaster/postmaster.c | 35 +++++++++---- src/include/miscadmin.h | 2 + src/include/pgstat.h | 1 + src/include/postmaster/pgarch.h | 4 +- 7 files changed, 67 insertions(+), 87 deletions(-) diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c index 4d7ed8ad1a..b0878a3dd9 100644 --- a/src/backend/bootstrap/bootstrap.c +++ b/src/backend/bootstrap/bootstrap.c @@ -328,6 +328,9 @@ AuxiliaryProcessMain(int argc, char *argv[]) case BgWriterProcess: statmsg = pgstat_get_backend_desc(B_BG_WRITER); break; + case ArchiverProcess: + statmsg = pgstat_get_backend_desc(B_ARCHIVER); + break; case CheckpointerProcess: statmsg = pgstat_get_backend_desc(B_CHECKPOINTER); break; @@ -455,6 +458,11 @@ AuxiliaryProcessMain(int argc, char *argv[]) BackgroundWriterMain(); proc_exit(1); /* should never return */ + case ArchiverProcess: + /* don't set signals, archiver has its own agenda */ + PgArchiverMain(); + proc_exit(1); /* should never return */ + case CheckpointerProcess: /* don't set signals, checkpointer has its own agenda */ CheckpointerMain(); diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c index f84f882c4c..4342ebdab4 100644 --- a/src/backend/postmaster/pgarch.c +++ b/src/backend/postmaster/pgarch.c @@ -77,7 +77,6 @@ * Local data * ---------- */ -static time_t last_pgarch_start_time; static time_t last_sigterm_time = 0; /* @@ -96,7 +95,6 @@ static volatile sig_atomic_t ready_to_stop = false; static pid_t pgarch_forkexec(void); #endif -NON_EXEC_STATIC void PgArchiverMain(int argc, char *argv[]) pg_attribute_noreturn(); static void pgarch_exit(SIGNAL_ARGS); static void ArchSigHupHandler(SIGNAL_ARGS); static void ArchSigTermHandler(SIGNAL_ARGS); @@ -114,75 +112,6 @@ static void pgarch_archiveDone(char *xlog); * ------------------------------------------------------------ */ -/* - * pgarch_start - * - * Called from postmaster at startup or after an existing archiver - * died. Attempt to fire up a fresh archiver process. - * - * Returns PID of child process, or 0 if fail. - * - * Note: if fail, we will be called again from the postmaster main loop. - */ -int -pgarch_start(void) -{ - time_t curtime; - pid_t pgArchPid; - - /* - * Do nothing if no archiver needed - */ - if (!XLogArchivingActive()) - return 0; - - /* - * Do nothing if too soon since last archiver start. This is a safety - * valve to protect against continuous respawn attempts if the archiver is - * dying immediately at launch. Note that since we will be re-called from - * the postmaster main loop, we will get another chance later. - */ - curtime = time(NULL); - if ((unsigned int) (curtime - last_pgarch_start_time) < - (unsigned int) PGARCH_RESTART_INTERVAL) - return 0; - last_pgarch_start_time = curtime; - -#ifdef EXEC_BACKEND - switch ((pgArchPid = pgarch_forkexec())) -#else - switch ((pgArchPid = fork_process())) -#endif - { - case -1: - ereport(LOG, - (errmsg("could not fork archiver: %m"))); - return 0; - -#ifndef EXEC_BACKEND - case 0: - /* in postmaster child ... */ - InitPostmasterChild(); - - /* Close the postmaster's sockets */ - ClosePostmasterPorts(false); - - /* Drop our connection to postmaster's shared memory, as well */ - dsm_detach_all(); - PGSharedMemoryDetach(); - - PgArchiverMain(0, NULL); - break; -#endif - - default: - return (int) pgArchPid; - } - - /* shouldn't get here */ - return 0; -} - /* ------------------------------------------------------------ * Local functions called by archiver follow * ------------------------------------------------------------ @@ -222,8 +151,8 @@ pgarch_forkexec(void) * The argc/argv parameters are valid only in EXEC_BACKEND case. However, * since we don't use 'em, it hardly matters... */ -NON_EXEC_STATIC void -PgArchiverMain(int argc, char *argv[]) +void +PgArchiverMain(void) { /* * Ignore all signals usually bound to some action in the postmaster, @@ -255,8 +184,27 @@ PgArchiverMain(int argc, char *argv[]) static void pgarch_exit(SIGNAL_ARGS) { - /* SIGQUIT means curl up and die ... */ - exit(1); + PG_SETMASK(&BlockSig); + + /* + * We DO NOT want to run proc_exit() callbacks -- we're here because + * shared memory may be corrupted, so we don't want to try to clean up our + * transaction. Just nail the windows shut and get out of town. Now that + * there's an atexit callback to prevent third-party code from breaking + * things by calling exit() directly, we have to reset the callbacks + * explicitly to make this work as intended. + */ + on_exit_reset(); + + /* + * Note we do exit(2) not exit(0). This is to force the postmaster into a + * system reset cycle if some idiot DBA sends a manual SIGQUIT to a random + * backend. This is necessary precisely because we don't clean up our + * shared memory state. (The "dead man switch" mechanism in pmsignal.c + * should ensure the postmaster sees this as a crash, too, but no harm in + * being doubly sure.) + */ + exit(2); } /* SIGHUP signal handler for archiver process */ diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c index 81c6499251..8d3c45dd4e 100644 --- a/src/backend/postmaster/pgstat.c +++ b/src/backend/postmaster/pgstat.c @@ -2856,6 +2856,9 @@ pgstat_bestart(void) case BgWriterProcess: beentry->st_backendType = B_BG_WRITER; break; + case ArchiverProcess: + beentry->st_backendType = B_ARCHIVER; + break; case CheckpointerProcess: beentry->st_backendType = B_CHECKPOINTER; break; @@ -4105,6 +4108,9 @@ pgstat_get_backend_desc(BackendType backendType) switch (backendType) { + case B_ARCHIVER: + backendDesc = "archiver"; + break; case B_AUTOVAC_LAUNCHER: backendDesc = "autovacuum launcher"; break; diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index ccea231e98..820f356038 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -146,7 +146,8 @@ #define BACKEND_TYPE_AUTOVAC 0x0002 /* autovacuum worker process */ #define BACKEND_TYPE_WALSND 0x0004 /* walsender process */ #define BACKEND_TYPE_BGWORKER 0x0008 /* bgworker process */ -#define BACKEND_TYPE_ALL 0x000F /* OR of all the above */ +#define BACKEND_TYPE_ARCHIVER 0x0010 /* archiver process */ +#define BACKEND_TYPE_ALL 0x001F /* OR of all the above */ #define BACKEND_TYPE_WORKER (BACKEND_TYPE_AUTOVAC | BACKEND_TYPE_BGWORKER) @@ -538,6 +539,7 @@ static void ShmemBackendArrayRemove(Backend *bn); #endif /* EXEC_BACKEND */ #define StartupDataBase() StartChildProcess(StartupProcess) +#define StartArchiver() StartChildProcess(ArchiverProcess) #define StartBackgroundWriter() StartChildProcess(BgWriterProcess) #define StartCheckpointer() StartChildProcess(CheckpointerProcess) #define StartWalWriter() StartChildProcess(WalWriterProcess) @@ -1761,7 +1763,7 @@ ServerLoop(void) /* If we have lost the archiver, try to start a new one. */ if (PgArchPID == 0 && PgArchStartupAllowed()) - PgArchPID = pgarch_start(); + PgArchPID = StartArchiver(); /* If we need to signal the autovacuum launcher, do so now */ if (avlauncher_needs_signal) @@ -2924,7 +2926,7 @@ reaper(SIGNAL_ARGS) if (!IsBinaryUpgrade && AutoVacuumingActive() && AutoVacPID == 0) AutoVacPID = StartAutoVacLauncher(); if (PgArchStartupAllowed() && PgArchPID == 0) - PgArchPID = pgarch_start(); + PgArchPID = StartArchiver(); if (PgStatPID == 0) PgStatPID = pgstat_start(); @@ -3069,10 +3071,8 @@ reaper(SIGNAL_ARGS) { PgArchPID = 0; if (!EXIT_STATUS_0(exitstatus)) - LogChildExit(LOG, _("archiver process"), - pid, exitstatus); - if (PgArchStartupAllowed()) - PgArchPID = pgarch_start(); + HandleChildCrash(pid, exitstatus, + _("archiver process")); continue; } @@ -3318,7 +3318,7 @@ CleanupBackend(int pid, /* * HandleChildCrash -- cleanup after failed backend, bgwriter, checkpointer, - * walwriter, autovacuum, or background worker. + * walwriter, autovacuum, archiver or background worker. * * The objectives here are to clean up our local state about the child * process, and to signal all other remaining children to quickdie. @@ -3523,6 +3523,18 @@ HandleChildCrash(int pid, int exitstatus, const char *procname) signal_child(AutoVacPID, (SendStop ? SIGSTOP : SIGQUIT)); } + /* Take care of the archiver too */ + if (pid == PgArchPID) + PgArchPID = 0; + else if (PgArchPID != 0 && take_action) + { + ereport(DEBUG2, + (errmsg_internal("sending %s to process %d", + (SendStop ? "SIGSTOP" : "SIGQUIT"), + (int) PgArchPID))); + signal_child(PgArchPID, (SendStop ? SIGSTOP : SIGQUIT)); + } + /* * Force a power-cycle of the pgarch process too. (This isn't absolutely * necessary, but it seems like a good idea for robustness, and it @@ -3799,6 +3811,7 @@ PostmasterStateMachine(void) Assert(CheckpointerPID == 0); Assert(WalWriterPID == 0); Assert(AutoVacPID == 0); + Assert(PgArchPID == 0); /* syslogger is not considered here */ pmState = PM_NO_CHILDREN; } @@ -5068,7 +5081,7 @@ sigusr1_handler(SIGNAL_ARGS) */ Assert(PgArchPID == 0); if (XLogArchivingAlways()) - PgArchPID = pgarch_start(); + PgArchPID = StartArchiver(); /* * If we aren't planning to enter hot standby mode later, treat @@ -5342,6 +5355,10 @@ StartChildProcess(AuxProcType type) ereport(LOG, (errmsg("could not fork startup process: %m"))); break; + case ArchiverProcess: + ereport(LOG, + (errmsg("could not fork archiver process: %m"))); + break; case BgWriterProcess: ereport(LOG, (errmsg("could not fork background writer process: %m"))); diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index c9e35003a5..63a7653457 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -399,6 +399,7 @@ typedef enum BootstrapProcess, StartupProcess, BgWriterProcess, + ArchiverProcess, CheckpointerProcess, WalWriterProcess, WalReceiverProcess, @@ -411,6 +412,7 @@ extern AuxProcType MyAuxProcType; #define AmBootstrapProcess() (MyAuxProcType == BootstrapProcess) #define AmStartupProcess() (MyAuxProcType == StartupProcess) #define AmBackgroundWriterProcess() (MyAuxProcType == BgWriterProcess) +#define AmArchiverProcess() (MyAuxProcType == ArchiverProcess) #define AmCheckpointerProcess() (MyAuxProcType == CheckpointerProcess) #define AmWalWriterProcess() (MyAuxProcType == WalWriterProcess) #define AmWalReceiverProcess() (MyAuxProcType == WalReceiverProcess) diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 88a75fb798..3324be8a81 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -701,6 +701,7 @@ typedef struct PgStat_GlobalStats */ typedef enum BackendType { + B_ARCHIVER, B_AUTOVAC_LAUNCHER, B_AUTOVAC_WORKER, B_BACKEND, diff --git a/src/include/postmaster/pgarch.h b/src/include/postmaster/pgarch.h index 2474eac26a..88f16863d4 100644 --- a/src/include/postmaster/pgarch.h +++ b/src/include/postmaster/pgarch.h @@ -32,8 +32,6 @@ */ extern int pgarch_start(void); -#ifdef EXEC_BACKEND -extern void PgArchiverMain(int argc, char *argv[]) pg_attribute_noreturn(); -#endif +extern void PgArchiverMain(void) pg_attribute_noreturn(); #endif /* _PGARCH_H */ -- 2.16.3 ----Next_Part(Mon_Feb_25_13_52_14_2019_191)-- Content-Type: Text/X-Patch; charset=us-ascii Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="v17-0004-Allow-dsm-to-use-on-postmaster.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH 3/5] Make archiver process an auxiliary process @ 2018-11-07 07:53 Kyotaro Horiguchi <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Kyotaro Horiguchi @ 2018-11-07 07:53 UTC (permalink / raw) This is a preliminary patch for shared-memory based stats collector. Archiver process must be a auxiliary process since it uses shared memory after stats data wes moved onto shared-memory. Make the process an auxiliary process in order to make it work. --- src/backend/bootstrap/bootstrap.c | 8 +++ src/backend/postmaster/pgarch.c | 98 +++++++++---------------------------- src/backend/postmaster/pgstat.c | 6 +++ src/backend/postmaster/postmaster.c | 35 +++++++++---- src/include/miscadmin.h | 2 + src/include/pgstat.h | 1 + src/include/postmaster/pgarch.h | 4 +- 7 files changed, 67 insertions(+), 87 deletions(-) diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c index 43627ab8f4..7872a2d9d7 100644 --- a/src/backend/bootstrap/bootstrap.c +++ b/src/backend/bootstrap/bootstrap.c @@ -329,6 +329,9 @@ AuxiliaryProcessMain(int argc, char *argv[]) case BgWriterProcess: statmsg = pgstat_get_backend_desc(B_BG_WRITER); break; + case ArchiverProcess: + statmsg = pgstat_get_backend_desc(B_ARCHIVER); + break; case CheckpointerProcess: statmsg = pgstat_get_backend_desc(B_CHECKPOINTER); break; @@ -456,6 +459,11 @@ AuxiliaryProcessMain(int argc, char *argv[]) BackgroundWriterMain(); proc_exit(1); /* should never return */ + case ArchiverProcess: + /* don't set signals, archiver has its own agenda */ + PgArchiverMain(); + proc_exit(1); /* should never return */ + case CheckpointerProcess: /* don't set signals, checkpointer has its own agenda */ CheckpointerMain(); diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c index f84f882c4c..4342ebdab4 100644 --- a/src/backend/postmaster/pgarch.c +++ b/src/backend/postmaster/pgarch.c @@ -77,7 +77,6 @@ * Local data * ---------- */ -static time_t last_pgarch_start_time; static time_t last_sigterm_time = 0; /* @@ -96,7 +95,6 @@ static volatile sig_atomic_t ready_to_stop = false; static pid_t pgarch_forkexec(void); #endif -NON_EXEC_STATIC void PgArchiverMain(int argc, char *argv[]) pg_attribute_noreturn(); static void pgarch_exit(SIGNAL_ARGS); static void ArchSigHupHandler(SIGNAL_ARGS); static void ArchSigTermHandler(SIGNAL_ARGS); @@ -114,75 +112,6 @@ static void pgarch_archiveDone(char *xlog); * ------------------------------------------------------------ */ -/* - * pgarch_start - * - * Called from postmaster at startup or after an existing archiver - * died. Attempt to fire up a fresh archiver process. - * - * Returns PID of child process, or 0 if fail. - * - * Note: if fail, we will be called again from the postmaster main loop. - */ -int -pgarch_start(void) -{ - time_t curtime; - pid_t pgArchPid; - - /* - * Do nothing if no archiver needed - */ - if (!XLogArchivingActive()) - return 0; - - /* - * Do nothing if too soon since last archiver start. This is a safety - * valve to protect against continuous respawn attempts if the archiver is - * dying immediately at launch. Note that since we will be re-called from - * the postmaster main loop, we will get another chance later. - */ - curtime = time(NULL); - if ((unsigned int) (curtime - last_pgarch_start_time) < - (unsigned int) PGARCH_RESTART_INTERVAL) - return 0; - last_pgarch_start_time = curtime; - -#ifdef EXEC_BACKEND - switch ((pgArchPid = pgarch_forkexec())) -#else - switch ((pgArchPid = fork_process())) -#endif - { - case -1: - ereport(LOG, - (errmsg("could not fork archiver: %m"))); - return 0; - -#ifndef EXEC_BACKEND - case 0: - /* in postmaster child ... */ - InitPostmasterChild(); - - /* Close the postmaster's sockets */ - ClosePostmasterPorts(false); - - /* Drop our connection to postmaster's shared memory, as well */ - dsm_detach_all(); - PGSharedMemoryDetach(); - - PgArchiverMain(0, NULL); - break; -#endif - - default: - return (int) pgArchPid; - } - - /* shouldn't get here */ - return 0; -} - /* ------------------------------------------------------------ * Local functions called by archiver follow * ------------------------------------------------------------ @@ -222,8 +151,8 @@ pgarch_forkexec(void) * The argc/argv parameters are valid only in EXEC_BACKEND case. However, * since we don't use 'em, it hardly matters... */ -NON_EXEC_STATIC void -PgArchiverMain(int argc, char *argv[]) +void +PgArchiverMain(void) { /* * Ignore all signals usually bound to some action in the postmaster, @@ -255,8 +184,27 @@ PgArchiverMain(int argc, char *argv[]) static void pgarch_exit(SIGNAL_ARGS) { - /* SIGQUIT means curl up and die ... */ - exit(1); + PG_SETMASK(&BlockSig); + + /* + * We DO NOT want to run proc_exit() callbacks -- we're here because + * shared memory may be corrupted, so we don't want to try to clean up our + * transaction. Just nail the windows shut and get out of town. Now that + * there's an atexit callback to prevent third-party code from breaking + * things by calling exit() directly, we have to reset the callbacks + * explicitly to make this work as intended. + */ + on_exit_reset(); + + /* + * Note we do exit(2) not exit(0). This is to force the postmaster into a + * system reset cycle if some idiot DBA sends a manual SIGQUIT to a random + * backend. This is necessary precisely because we don't clean up our + * shared memory state. (The "dead man switch" mechanism in pmsignal.c + * should ensure the postmaster sees this as a crash, too, but no harm in + * being doubly sure.) + */ + exit(2); } /* SIGHUP signal handler for archiver process */ diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c index 285def556b..ccb1d0b62e 100644 --- a/src/backend/postmaster/pgstat.c +++ b/src/backend/postmaster/pgstat.c @@ -2934,6 +2934,9 @@ pgstat_bestart(void) case StartupProcess: lbeentry.st_backendType = B_STARTUP; break; + case ArchiverProcess: + beentry->st_backendType = B_ARCHIVER; + break; case BgWriterProcess: lbeentry.st_backendType = B_BG_WRITER; break; @@ -4277,6 +4280,9 @@ pgstat_get_backend_desc(BackendType backendType) switch (backendType) { + case B_ARCHIVER: + backendDesc = "archiver"; + break; case B_AUTOVAC_LAUNCHER: backendDesc = "autovacuum launcher"; break; diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index 34315b8d1a..ec9a7ca311 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -146,7 +146,8 @@ #define BACKEND_TYPE_AUTOVAC 0x0002 /* autovacuum worker process */ #define BACKEND_TYPE_WALSND 0x0004 /* walsender process */ #define BACKEND_TYPE_BGWORKER 0x0008 /* bgworker process */ -#define BACKEND_TYPE_ALL 0x000F /* OR of all the above */ +#define BACKEND_TYPE_ARCHIVER 0x0010 /* archiver process */ +#define BACKEND_TYPE_ALL 0x001F /* OR of all the above */ #define BACKEND_TYPE_WORKER (BACKEND_TYPE_AUTOVAC | BACKEND_TYPE_BGWORKER) @@ -539,6 +540,7 @@ static void ShmemBackendArrayRemove(Backend *bn); #endif /* EXEC_BACKEND */ #define StartupDataBase() StartChildProcess(StartupProcess) +#define StartArchiver() StartChildProcess(ArchiverProcess) #define StartBackgroundWriter() StartChildProcess(BgWriterProcess) #define StartCheckpointer() StartChildProcess(CheckpointerProcess) #define StartWalWriter() StartChildProcess(WalWriterProcess) @@ -1762,7 +1764,7 @@ ServerLoop(void) /* If we have lost the archiver, try to start a new one. */ if (PgArchPID == 0 && PgArchStartupAllowed()) - PgArchPID = pgarch_start(); + PgArchPID = StartArchiver(); /* If we need to signal the autovacuum launcher, do so now */ if (avlauncher_needs_signal) @@ -2977,7 +2979,7 @@ reaper(SIGNAL_ARGS) if (!IsBinaryUpgrade && AutoVacuumingActive() && AutoVacPID == 0) AutoVacPID = StartAutoVacLauncher(); if (PgArchStartupAllowed() && PgArchPID == 0) - PgArchPID = pgarch_start(); + PgArchPID = StartArchiver(); if (PgStatPID == 0) PgStatPID = pgstat_start(); @@ -3122,10 +3124,8 @@ reaper(SIGNAL_ARGS) { PgArchPID = 0; if (!EXIT_STATUS_0(exitstatus)) - LogChildExit(LOG, _("archiver process"), - pid, exitstatus); - if (PgArchStartupAllowed()) - PgArchPID = pgarch_start(); + HandleChildCrash(pid, exitstatus, + _("archiver process")); continue; } @@ -3371,7 +3371,7 @@ CleanupBackend(int pid, /* * HandleChildCrash -- cleanup after failed backend, bgwriter, checkpointer, - * walwriter, autovacuum, or background worker. + * walwriter, autovacuum, archiver or background worker. * * The objectives here are to clean up our local state about the child * process, and to signal all other remaining children to quickdie. @@ -3576,6 +3576,18 @@ HandleChildCrash(int pid, int exitstatus, const char *procname) signal_child(AutoVacPID, (SendStop ? SIGSTOP : SIGQUIT)); } + /* Take care of the archiver too */ + if (pid == PgArchPID) + PgArchPID = 0; + else if (PgArchPID != 0 && take_action) + { + ereport(DEBUG2, + (errmsg_internal("sending %s to process %d", + (SendStop ? "SIGSTOP" : "SIGQUIT"), + (int) PgArchPID))); + signal_child(PgArchPID, (SendStop ? SIGSTOP : SIGQUIT)); + } + /* * Force a power-cycle of the pgarch process too. (This isn't absolutely * necessary, but it seems like a good idea for robustness, and it @@ -3848,6 +3860,7 @@ PostmasterStateMachine(void) Assert(CheckpointerPID == 0); Assert(WalWriterPID == 0); Assert(AutoVacPID == 0); + Assert(PgArchPID == 0); /* syslogger is not considered here */ pmState = PM_NO_CHILDREN; } @@ -5117,7 +5130,7 @@ sigusr1_handler(SIGNAL_ARGS) */ Assert(PgArchPID == 0); if (XLogArchivingAlways()) - PgArchPID = pgarch_start(); + PgArchPID = StartArchiver(); /* * If we aren't planning to enter hot standby mode later, treat @@ -5400,6 +5413,10 @@ StartChildProcess(AuxProcType type) ereport(LOG, (errmsg("could not fork startup process: %m"))); break; + case ArchiverProcess: + ereport(LOG, + (errmsg("could not fork archiver process: %m"))); + break; case BgWriterProcess: ereport(LOG, (errmsg("could not fork background writer process: %m"))); diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index b677c7e821..c33abee7aa 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -399,6 +399,7 @@ typedef enum BootstrapProcess, StartupProcess, BgWriterProcess, + ArchiverProcess, CheckpointerProcess, WalWriterProcess, WalReceiverProcess, @@ -411,6 +412,7 @@ extern AuxProcType MyAuxProcType; #define AmBootstrapProcess() (MyAuxProcType == BootstrapProcess) #define AmStartupProcess() (MyAuxProcType == StartupProcess) #define AmBackgroundWriterProcess() (MyAuxProcType == BgWriterProcess) +#define AmArchiverProcess() (MyAuxProcType == ArchiverProcess) #define AmCheckpointerProcess() (MyAuxProcType == CheckpointerProcess) #define AmWalWriterProcess() (MyAuxProcType == WalWriterProcess) #define AmWalReceiverProcess() (MyAuxProcType == WalReceiverProcess) diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 929987190c..f3d4cb5637 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -719,6 +719,7 @@ typedef struct PgStat_GlobalStats */ typedef enum BackendType { + B_ARCHIVER, B_AUTOVAC_LAUNCHER, B_AUTOVAC_WORKER, B_BACKEND, diff --git a/src/include/postmaster/pgarch.h b/src/include/postmaster/pgarch.h index 2474eac26a..88f16863d4 100644 --- a/src/include/postmaster/pgarch.h +++ b/src/include/postmaster/pgarch.h @@ -32,8 +32,6 @@ */ extern int pgarch_start(void); -#ifdef EXEC_BACKEND -extern void PgArchiverMain(int argc, char *argv[]) pg_attribute_noreturn(); -#endif +extern void PgArchiverMain(void) pg_attribute_noreturn(); #endif /* _PGARCH_H */ -- 2.16.3 ----Next_Part(Fri_May_17_14_27_22_2019_078)-- Content-Type: Text/X-Patch; charset=us-ascii Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="v19-0004-Shared-memory-based-stats-collector.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v23 3/5] Make archiver process an auxiliary process @ 2018-11-07 07:53 Kyotaro Horiguchi <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Kyotaro Horiguchi @ 2018-11-07 07:53 UTC (permalink / raw) This is a preliminary patch for shared-memory based stats collector. Archiver process must be a auxiliary process since it uses shared memory after stats data wes moved onto shared-memory. Make the process an auxiliary process in order to make it work. --- src/backend/bootstrap/bootstrap.c | 8 +++ src/backend/postmaster/pgarch.c | 98 +++++++++---------------------------- src/backend/postmaster/pgstat.c | 6 +++ src/backend/postmaster/postmaster.c | 35 +++++++++---- src/include/miscadmin.h | 2 + src/include/pgstat.h | 1 + src/include/postmaster/pgarch.h | 4 +- 7 files changed, 67 insertions(+), 87 deletions(-) diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c index 9238fbe98d..dde2485b14 100644 --- a/src/backend/bootstrap/bootstrap.c +++ b/src/backend/bootstrap/bootstrap.c @@ -329,6 +329,9 @@ AuxiliaryProcessMain(int argc, char *argv[]) case BgWriterProcess: statmsg = pgstat_get_backend_desc(B_BG_WRITER); break; + case ArchiverProcess: + statmsg = pgstat_get_backend_desc(B_ARCHIVER); + break; case CheckpointerProcess: statmsg = pgstat_get_backend_desc(B_CHECKPOINTER); break; @@ -456,6 +459,11 @@ AuxiliaryProcessMain(int argc, char *argv[]) BackgroundWriterMain(); proc_exit(1); /* should never return */ + case ArchiverProcess: + /* don't set signals, archiver has its own agenda */ + PgArchiverMain(); + proc_exit(1); /* should never return */ + case CheckpointerProcess: /* don't set signals, checkpointer has its own agenda */ CheckpointerMain(); diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c index f84f882c4c..4342ebdab4 100644 --- a/src/backend/postmaster/pgarch.c +++ b/src/backend/postmaster/pgarch.c @@ -77,7 +77,6 @@ * Local data * ---------- */ -static time_t last_pgarch_start_time; static time_t last_sigterm_time = 0; /* @@ -96,7 +95,6 @@ static volatile sig_atomic_t ready_to_stop = false; static pid_t pgarch_forkexec(void); #endif -NON_EXEC_STATIC void PgArchiverMain(int argc, char *argv[]) pg_attribute_noreturn(); static void pgarch_exit(SIGNAL_ARGS); static void ArchSigHupHandler(SIGNAL_ARGS); static void ArchSigTermHandler(SIGNAL_ARGS); @@ -114,75 +112,6 @@ static void pgarch_archiveDone(char *xlog); * ------------------------------------------------------------ */ -/* - * pgarch_start - * - * Called from postmaster at startup or after an existing archiver - * died. Attempt to fire up a fresh archiver process. - * - * Returns PID of child process, or 0 if fail. - * - * Note: if fail, we will be called again from the postmaster main loop. - */ -int -pgarch_start(void) -{ - time_t curtime; - pid_t pgArchPid; - - /* - * Do nothing if no archiver needed - */ - if (!XLogArchivingActive()) - return 0; - - /* - * Do nothing if too soon since last archiver start. This is a safety - * valve to protect against continuous respawn attempts if the archiver is - * dying immediately at launch. Note that since we will be re-called from - * the postmaster main loop, we will get another chance later. - */ - curtime = time(NULL); - if ((unsigned int) (curtime - last_pgarch_start_time) < - (unsigned int) PGARCH_RESTART_INTERVAL) - return 0; - last_pgarch_start_time = curtime; - -#ifdef EXEC_BACKEND - switch ((pgArchPid = pgarch_forkexec())) -#else - switch ((pgArchPid = fork_process())) -#endif - { - case -1: - ereport(LOG, - (errmsg("could not fork archiver: %m"))); - return 0; - -#ifndef EXEC_BACKEND - case 0: - /* in postmaster child ... */ - InitPostmasterChild(); - - /* Close the postmaster's sockets */ - ClosePostmasterPorts(false); - - /* Drop our connection to postmaster's shared memory, as well */ - dsm_detach_all(); - PGSharedMemoryDetach(); - - PgArchiverMain(0, NULL); - break; -#endif - - default: - return (int) pgArchPid; - } - - /* shouldn't get here */ - return 0; -} - /* ------------------------------------------------------------ * Local functions called by archiver follow * ------------------------------------------------------------ @@ -222,8 +151,8 @@ pgarch_forkexec(void) * The argc/argv parameters are valid only in EXEC_BACKEND case. However, * since we don't use 'em, it hardly matters... */ -NON_EXEC_STATIC void -PgArchiverMain(int argc, char *argv[]) +void +PgArchiverMain(void) { /* * Ignore all signals usually bound to some action in the postmaster, @@ -255,8 +184,27 @@ PgArchiverMain(int argc, char *argv[]) static void pgarch_exit(SIGNAL_ARGS) { - /* SIGQUIT means curl up and die ... */ - exit(1); + PG_SETMASK(&BlockSig); + + /* + * We DO NOT want to run proc_exit() callbacks -- we're here because + * shared memory may be corrupted, so we don't want to try to clean up our + * transaction. Just nail the windows shut and get out of town. Now that + * there's an atexit callback to prevent third-party code from breaking + * things by calling exit() directly, we have to reset the callbacks + * explicitly to make this work as intended. + */ + on_exit_reset(); + + /* + * Note we do exit(2) not exit(0). This is to force the postmaster into a + * system reset cycle if some idiot DBA sends a manual SIGQUIT to a random + * backend. This is necessary precisely because we don't clean up our + * shared memory state. (The "dead man switch" mechanism in pmsignal.c + * should ensure the postmaster sees this as a crash, too, but no harm in + * being doubly sure.) + */ + exit(2); } /* SIGHUP signal handler for archiver process */ diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c index 011076c3e3..043e3ff9d2 100644 --- a/src/backend/postmaster/pgstat.c +++ b/src/backend/postmaster/pgstat.c @@ -2934,6 +2934,9 @@ pgstat_bestart(void) case StartupProcess: lbeentry.st_backendType = B_STARTUP; break; + case ArchiverProcess: + beentry->st_backendType = B_ARCHIVER; + break; case BgWriterProcess: lbeentry.st_backendType = B_BG_WRITER; break; @@ -4277,6 +4280,9 @@ pgstat_get_backend_desc(BackendType backendType) switch (backendType) { + case B_ARCHIVER: + backendDesc = "archiver"; + break; case B_AUTOVAC_LAUNCHER: backendDesc = "autovacuum launcher"; break; diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index eb9e0221f8..27a9e45074 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -146,7 +146,8 @@ #define BACKEND_TYPE_AUTOVAC 0x0002 /* autovacuum worker process */ #define BACKEND_TYPE_WALSND 0x0004 /* walsender process */ #define BACKEND_TYPE_BGWORKER 0x0008 /* bgworker process */ -#define BACKEND_TYPE_ALL 0x000F /* OR of all the above */ +#define BACKEND_TYPE_ARCHIVER 0x0010 /* archiver process */ +#define BACKEND_TYPE_ALL 0x001F /* OR of all the above */ #define BACKEND_TYPE_WORKER (BACKEND_TYPE_AUTOVAC | BACKEND_TYPE_BGWORKER) @@ -539,6 +540,7 @@ static void ShmemBackendArrayRemove(Backend *bn); #endif /* EXEC_BACKEND */ #define StartupDataBase() StartChildProcess(StartupProcess) +#define StartArchiver() StartChildProcess(ArchiverProcess) #define StartBackgroundWriter() StartChildProcess(BgWriterProcess) #define StartCheckpointer() StartChildProcess(CheckpointerProcess) #define StartWalWriter() StartChildProcess(WalWriterProcess) @@ -1776,7 +1778,7 @@ ServerLoop(void) /* If we have lost the archiver, try to start a new one. */ if (PgArchPID == 0 && PgArchStartupAllowed()) - PgArchPID = pgarch_start(); + PgArchPID = StartArchiver(); /* If we need to signal the autovacuum launcher, do so now */ if (avlauncher_needs_signal) @@ -3005,7 +3007,7 @@ reaper(SIGNAL_ARGS) if (!IsBinaryUpgrade && AutoVacuumingActive() && AutoVacPID == 0) AutoVacPID = StartAutoVacLauncher(); if (PgArchStartupAllowed() && PgArchPID == 0) - PgArchPID = pgarch_start(); + PgArchPID = StartArchiver(); if (PgStatPID == 0) PgStatPID = pgstat_start(); @@ -3150,10 +3152,8 @@ reaper(SIGNAL_ARGS) { PgArchPID = 0; if (!EXIT_STATUS_0(exitstatus)) - LogChildExit(LOG, _("archiver process"), - pid, exitstatus); - if (PgArchStartupAllowed()) - PgArchPID = pgarch_start(); + HandleChildCrash(pid, exitstatus, + _("archiver process")); continue; } @@ -3399,7 +3399,7 @@ CleanupBackend(int pid, /* * HandleChildCrash -- cleanup after failed backend, bgwriter, checkpointer, - * walwriter, autovacuum, or background worker. + * walwriter, autovacuum, archiver or background worker. * * The objectives here are to clean up our local state about the child * process, and to signal all other remaining children to quickdie. @@ -3604,6 +3604,18 @@ HandleChildCrash(int pid, int exitstatus, const char *procname) signal_child(AutoVacPID, (SendStop ? SIGSTOP : SIGQUIT)); } + /* Take care of the archiver too */ + if (pid == PgArchPID) + PgArchPID = 0; + else if (PgArchPID != 0 && take_action) + { + ereport(DEBUG2, + (errmsg_internal("sending %s to process %d", + (SendStop ? "SIGSTOP" : "SIGQUIT"), + (int) PgArchPID))); + signal_child(PgArchPID, (SendStop ? SIGSTOP : SIGQUIT)); + } + /* * Force a power-cycle of the pgarch process too. (This isn't absolutely * necessary, but it seems like a good idea for robustness, and it @@ -3876,6 +3888,7 @@ PostmasterStateMachine(void) Assert(CheckpointerPID == 0); Assert(WalWriterPID == 0); Assert(AutoVacPID == 0); + Assert(PgArchPID == 0); /* syslogger is not considered here */ pmState = PM_NO_CHILDREN; } @@ -5145,7 +5158,7 @@ sigusr1_handler(SIGNAL_ARGS) */ Assert(PgArchPID == 0); if (XLogArchivingAlways()) - PgArchPID = pgarch_start(); + PgArchPID = StartArchiver(); /* * If we aren't planning to enter hot standby mode later, treat @@ -5428,6 +5441,10 @@ StartChildProcess(AuxProcType type) ereport(LOG, (errmsg("could not fork startup process: %m"))); break; + case ArchiverProcess: + ereport(LOG, + (errmsg("could not fork archiver process: %m"))); + break; case BgWriterProcess: ereport(LOG, (errmsg("could not fork background writer process: %m"))); diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index bc6e03fbc7..1f4db67f3f 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -399,6 +399,7 @@ typedef enum BootstrapProcess, StartupProcess, BgWriterProcess, + ArchiverProcess, CheckpointerProcess, WalWriterProcess, WalReceiverProcess, @@ -411,6 +412,7 @@ extern AuxProcType MyAuxProcType; #define AmBootstrapProcess() (MyAuxProcType == BootstrapProcess) #define AmStartupProcess() (MyAuxProcType == StartupProcess) #define AmBackgroundWriterProcess() (MyAuxProcType == BgWriterProcess) +#define AmArchiverProcess() (MyAuxProcType == ArchiverProcess) #define AmCheckpointerProcess() (MyAuxProcType == CheckpointerProcess) #define AmWalWriterProcess() (MyAuxProcType == WalWriterProcess) #define AmWalReceiverProcess() (MyAuxProcType == WalReceiverProcess) diff --git a/src/include/pgstat.h b/src/include/pgstat.h index fe076d823d..65713abc2b 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -718,6 +718,7 @@ typedef struct PgStat_GlobalStats */ typedef enum BackendType { + B_ARCHIVER, B_AUTOVAC_LAUNCHER, B_AUTOVAC_WORKER, B_BACKEND, diff --git a/src/include/postmaster/pgarch.h b/src/include/postmaster/pgarch.h index 2474eac26a..88f16863d4 100644 --- a/src/include/postmaster/pgarch.h +++ b/src/include/postmaster/pgarch.h @@ -32,8 +32,6 @@ */ extern int pgarch_start(void); -#ifdef EXEC_BACKEND -extern void PgArchiverMain(int argc, char *argv[]) pg_attribute_noreturn(); -#endif +extern void PgArchiverMain(void) pg_attribute_noreturn(); #endif /* _PGARCH_H */ -- 2.16.3 ----Next_Part(Fri_Sep_27_09_46_47_2019_292)-- Content-Type: Text/X-Patch; charset=us-ascii Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="v23-0004-Shared-memory-based-stats-collector.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH 3/5] Make archiver process an auxiliary process @ 2018-11-07 07:53 Kyotaro Horiguchi <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Kyotaro Horiguchi @ 2018-11-07 07:53 UTC (permalink / raw) This is a preliminary patch for shared-memory based stats collector. Archiver process must be a auxiliary process since it uses shared memory after stats data wes moved onto shared-memory. Make the process an auxiliary process in order to make it work. --- src/backend/bootstrap/bootstrap.c | 8 +++ src/backend/postmaster/pgarch.c | 98 +++++++++---------------------------- src/backend/postmaster/pgstat.c | 6 +++ src/backend/postmaster/postmaster.c | 35 +++++++++---- src/include/miscadmin.h | 2 + src/include/pgstat.h | 1 + src/include/postmaster/pgarch.h | 4 +- 7 files changed, 67 insertions(+), 87 deletions(-) diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c index 4d7ed8ad1a..a6c3338d40 100644 --- a/src/backend/bootstrap/bootstrap.c +++ b/src/backend/bootstrap/bootstrap.c @@ -328,6 +328,9 @@ AuxiliaryProcessMain(int argc, char *argv[]) case BgWriterProcess: statmsg = pgstat_get_backend_desc(B_BG_WRITER); break; + case ArchiverProcess: + statmsg = pgstat_get_backend_desc(B_ARCHIVER); + break; case CheckpointerProcess: statmsg = pgstat_get_backend_desc(B_CHECKPOINTER); break; @@ -455,6 +458,11 @@ AuxiliaryProcessMain(int argc, char *argv[]) BackgroundWriterMain(); proc_exit(1); /* should never return */ + case ArchiverProcess: + /* don't set signals, bgwriter has its own agenda */ + PgArchiverMain(); + proc_exit(1); /* should never return */ + case CheckpointerProcess: /* don't set signals, checkpointer has its own agenda */ CheckpointerMain(); diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c index f84f882c4c..4342ebdab4 100644 --- a/src/backend/postmaster/pgarch.c +++ b/src/backend/postmaster/pgarch.c @@ -77,7 +77,6 @@ * Local data * ---------- */ -static time_t last_pgarch_start_time; static time_t last_sigterm_time = 0; /* @@ -96,7 +95,6 @@ static volatile sig_atomic_t ready_to_stop = false; static pid_t pgarch_forkexec(void); #endif -NON_EXEC_STATIC void PgArchiverMain(int argc, char *argv[]) pg_attribute_noreturn(); static void pgarch_exit(SIGNAL_ARGS); static void ArchSigHupHandler(SIGNAL_ARGS); static void ArchSigTermHandler(SIGNAL_ARGS); @@ -114,75 +112,6 @@ static void pgarch_archiveDone(char *xlog); * ------------------------------------------------------------ */ -/* - * pgarch_start - * - * Called from postmaster at startup or after an existing archiver - * died. Attempt to fire up a fresh archiver process. - * - * Returns PID of child process, or 0 if fail. - * - * Note: if fail, we will be called again from the postmaster main loop. - */ -int -pgarch_start(void) -{ - time_t curtime; - pid_t pgArchPid; - - /* - * Do nothing if no archiver needed - */ - if (!XLogArchivingActive()) - return 0; - - /* - * Do nothing if too soon since last archiver start. This is a safety - * valve to protect against continuous respawn attempts if the archiver is - * dying immediately at launch. Note that since we will be re-called from - * the postmaster main loop, we will get another chance later. - */ - curtime = time(NULL); - if ((unsigned int) (curtime - last_pgarch_start_time) < - (unsigned int) PGARCH_RESTART_INTERVAL) - return 0; - last_pgarch_start_time = curtime; - -#ifdef EXEC_BACKEND - switch ((pgArchPid = pgarch_forkexec())) -#else - switch ((pgArchPid = fork_process())) -#endif - { - case -1: - ereport(LOG, - (errmsg("could not fork archiver: %m"))); - return 0; - -#ifndef EXEC_BACKEND - case 0: - /* in postmaster child ... */ - InitPostmasterChild(); - - /* Close the postmaster's sockets */ - ClosePostmasterPorts(false); - - /* Drop our connection to postmaster's shared memory, as well */ - dsm_detach_all(); - PGSharedMemoryDetach(); - - PgArchiverMain(0, NULL); - break; -#endif - - default: - return (int) pgArchPid; - } - - /* shouldn't get here */ - return 0; -} - /* ------------------------------------------------------------ * Local functions called by archiver follow * ------------------------------------------------------------ @@ -222,8 +151,8 @@ pgarch_forkexec(void) * The argc/argv parameters are valid only in EXEC_BACKEND case. However, * since we don't use 'em, it hardly matters... */ -NON_EXEC_STATIC void -PgArchiverMain(int argc, char *argv[]) +void +PgArchiverMain(void) { /* * Ignore all signals usually bound to some action in the postmaster, @@ -255,8 +184,27 @@ PgArchiverMain(int argc, char *argv[]) static void pgarch_exit(SIGNAL_ARGS) { - /* SIGQUIT means curl up and die ... */ - exit(1); + PG_SETMASK(&BlockSig); + + /* + * We DO NOT want to run proc_exit() callbacks -- we're here because + * shared memory may be corrupted, so we don't want to try to clean up our + * transaction. Just nail the windows shut and get out of town. Now that + * there's an atexit callback to prevent third-party code from breaking + * things by calling exit() directly, we have to reset the callbacks + * explicitly to make this work as intended. + */ + on_exit_reset(); + + /* + * Note we do exit(2) not exit(0). This is to force the postmaster into a + * system reset cycle if some idiot DBA sends a manual SIGQUIT to a random + * backend. This is necessary precisely because we don't clean up our + * shared memory state. (The "dead man switch" mechanism in pmsignal.c + * should ensure the postmaster sees this as a crash, too, but no harm in + * being doubly sure.) + */ + exit(2); } /* SIGHUP signal handler for archiver process */ diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c index 81c6499251..9e6bce8f6a 100644 --- a/src/backend/postmaster/pgstat.c +++ b/src/backend/postmaster/pgstat.c @@ -2856,6 +2856,9 @@ pgstat_bestart(void) case BgWriterProcess: beentry->st_backendType = B_BG_WRITER; break; + case ArchiverProcess: + beentry->st_backendType = B_ARCHIVER; + break; case CheckpointerProcess: beentry->st_backendType = B_CHECKPOINTER; break; @@ -4120,6 +4123,9 @@ pgstat_get_backend_desc(BackendType backendType) case B_BG_WRITER: backendDesc = "background writer"; break; + case B_ARCHIVER: + backendDesc = "archiver"; + break; case B_CHECKPOINTER: backendDesc = "checkpointer"; break; diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index ccea231e98..a663a62fd5 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -146,7 +146,8 @@ #define BACKEND_TYPE_AUTOVAC 0x0002 /* autovacuum worker process */ #define BACKEND_TYPE_WALSND 0x0004 /* walsender process */ #define BACKEND_TYPE_BGWORKER 0x0008 /* bgworker process */ -#define BACKEND_TYPE_ALL 0x000F /* OR of all the above */ +#define BACKEND_TYPE_ARCHIVER 0x0010 /* archiver process */ +#define BACKEND_TYPE_ALL 0x001F /* OR of all the above */ #define BACKEND_TYPE_WORKER (BACKEND_TYPE_AUTOVAC | BACKEND_TYPE_BGWORKER) @@ -539,6 +540,7 @@ static void ShmemBackendArrayRemove(Backend *bn); #define StartupDataBase() StartChildProcess(StartupProcess) #define StartBackgroundWriter() StartChildProcess(BgWriterProcess) +#define StartArchiver() StartChildProcess(ArchiverProcess) #define StartCheckpointer() StartChildProcess(CheckpointerProcess) #define StartWalWriter() StartChildProcess(WalWriterProcess) #define StartWalReceiver() StartChildProcess(WalReceiverProcess) @@ -1761,7 +1763,7 @@ ServerLoop(void) /* If we have lost the archiver, try to start a new one. */ if (PgArchPID == 0 && PgArchStartupAllowed()) - PgArchPID = pgarch_start(); + PgArchPID = StartArchiver(); /* If we need to signal the autovacuum launcher, do so now */ if (avlauncher_needs_signal) @@ -2924,7 +2926,7 @@ reaper(SIGNAL_ARGS) if (!IsBinaryUpgrade && AutoVacuumingActive() && AutoVacPID == 0) AutoVacPID = StartAutoVacLauncher(); if (PgArchStartupAllowed() && PgArchPID == 0) - PgArchPID = pgarch_start(); + PgArchPID = StartArchiver(); if (PgStatPID == 0) PgStatPID = pgstat_start(); @@ -3069,10 +3071,8 @@ reaper(SIGNAL_ARGS) { PgArchPID = 0; if (!EXIT_STATUS_0(exitstatus)) - LogChildExit(LOG, _("archiver process"), - pid, exitstatus); - if (PgArchStartupAllowed()) - PgArchPID = pgarch_start(); + HandleChildCrash(pid, exitstatus, + _("archiver process")); continue; } @@ -3318,7 +3318,7 @@ CleanupBackend(int pid, /* * HandleChildCrash -- cleanup after failed backend, bgwriter, checkpointer, - * walwriter, autovacuum, or background worker. + * walwriter, autovacuum, archiver or background worker. * * The objectives here are to clean up our local state about the child * process, and to signal all other remaining children to quickdie. @@ -3523,6 +3523,18 @@ HandleChildCrash(int pid, int exitstatus, const char *procname) signal_child(AutoVacPID, (SendStop ? SIGSTOP : SIGQUIT)); } + /* Take care of the archiver too */ + if (pid == PgArchPID) + PgArchPID = 0; + else if (PgArchPID != 0 && take_action) + { + ereport(DEBUG2, + (errmsg_internal("sending %s to process %d", + (SendStop ? "SIGSTOP" : "SIGQUIT"), + (int) PgArchPID))); + signal_child(PgArchPID, (SendStop ? SIGSTOP : SIGQUIT)); + } + /* * Force a power-cycle of the pgarch process too. (This isn't absolutely * necessary, but it seems like a good idea for robustness, and it @@ -3799,6 +3811,7 @@ PostmasterStateMachine(void) Assert(CheckpointerPID == 0); Assert(WalWriterPID == 0); Assert(AutoVacPID == 0); + Assert(PgArchPID == 0); /* syslogger is not considered here */ pmState = PM_NO_CHILDREN; } @@ -5068,7 +5081,7 @@ sigusr1_handler(SIGNAL_ARGS) */ Assert(PgArchPID == 0); if (XLogArchivingAlways()) - PgArchPID = pgarch_start(); + PgArchPID = StartArchiver(); /* * If we aren't planning to enter hot standby mode later, treat @@ -5346,6 +5359,10 @@ StartChildProcess(AuxProcType type) ereport(LOG, (errmsg("could not fork background writer process: %m"))); break; + case ArchiverProcess: + ereport(LOG, + (errmsg("could not fork archiver process: %m"))); + break; case CheckpointerProcess: ereport(LOG, (errmsg("could not fork checkpointer process: %m"))); diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index c9e35003a5..63a7653457 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -399,6 +399,7 @@ typedef enum BootstrapProcess, StartupProcess, BgWriterProcess, + ArchiverProcess, CheckpointerProcess, WalWriterProcess, WalReceiverProcess, @@ -411,6 +412,7 @@ extern AuxProcType MyAuxProcType; #define AmBootstrapProcess() (MyAuxProcType == BootstrapProcess) #define AmStartupProcess() (MyAuxProcType == StartupProcess) #define AmBackgroundWriterProcess() (MyAuxProcType == BgWriterProcess) +#define AmArchiverProcess() (MyAuxProcType == ArchiverProcess) #define AmCheckpointerProcess() (MyAuxProcType == CheckpointerProcess) #define AmWalWriterProcess() (MyAuxProcType == WalWriterProcess) #define AmWalReceiverProcess() (MyAuxProcType == WalReceiverProcess) diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 88a75fb798..471877d2df 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -706,6 +706,7 @@ typedef enum BackendType B_BACKEND, B_BG_WORKER, B_BG_WRITER, + B_ARCHIVER, B_CHECKPOINTER, B_STARTUP, B_WAL_RECEIVER, diff --git a/src/include/postmaster/pgarch.h b/src/include/postmaster/pgarch.h index 2474eac26a..88f16863d4 100644 --- a/src/include/postmaster/pgarch.h +++ b/src/include/postmaster/pgarch.h @@ -32,8 +32,6 @@ */ extern int pgarch_start(void); -#ifdef EXEC_BACKEND -extern void PgArchiverMain(int argc, char *argv[]) pg_attribute_noreturn(); -#endif +extern void PgArchiverMain(void) pg_attribute_noreturn(); #endif /* _PGARCH_H */ -- 2.16.3 ----Next_Part(Tue_Feb_19_21_40_07_2019_247)-- Content-Type: Text/X-Patch; charset=us-ascii Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="v15-0004-Shared-memory-based-stats-collector.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH 3/5] Make archiver process an auxiliary process @ 2018-11-07 07:53 Kyotaro Horiguchi <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Kyotaro Horiguchi @ 2018-11-07 07:53 UTC (permalink / raw) This is a preliminary patch for shared-memory based stats collector. Archiver process must be a auxiliary process since it uses shared memory after stats data wes moved onto shared-memory. Make the process an auxiliary process in order to make it work. --- src/backend/bootstrap/bootstrap.c | 8 +++ src/backend/postmaster/pgarch.c | 98 +++++++++---------------------------- src/backend/postmaster/pgstat.c | 6 +++ src/backend/postmaster/postmaster.c | 35 +++++++++---- src/include/miscadmin.h | 2 + src/include/pgstat.h | 1 + src/include/postmaster/pgarch.h | 4 +- 7 files changed, 67 insertions(+), 87 deletions(-) diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c index 4d7ed8ad1a..a6c3338d40 100644 --- a/src/backend/bootstrap/bootstrap.c +++ b/src/backend/bootstrap/bootstrap.c @@ -328,6 +328,9 @@ AuxiliaryProcessMain(int argc, char *argv[]) case BgWriterProcess: statmsg = pgstat_get_backend_desc(B_BG_WRITER); break; + case ArchiverProcess: + statmsg = pgstat_get_backend_desc(B_ARCHIVER); + break; case CheckpointerProcess: statmsg = pgstat_get_backend_desc(B_CHECKPOINTER); break; @@ -455,6 +458,11 @@ AuxiliaryProcessMain(int argc, char *argv[]) BackgroundWriterMain(); proc_exit(1); /* should never return */ + case ArchiverProcess: + /* don't set signals, bgwriter has its own agenda */ + PgArchiverMain(); + proc_exit(1); /* should never return */ + case CheckpointerProcess: /* don't set signals, checkpointer has its own agenda */ CheckpointerMain(); diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c index f84f882c4c..4342ebdab4 100644 --- a/src/backend/postmaster/pgarch.c +++ b/src/backend/postmaster/pgarch.c @@ -77,7 +77,6 @@ * Local data * ---------- */ -static time_t last_pgarch_start_time; static time_t last_sigterm_time = 0; /* @@ -96,7 +95,6 @@ static volatile sig_atomic_t ready_to_stop = false; static pid_t pgarch_forkexec(void); #endif -NON_EXEC_STATIC void PgArchiverMain(int argc, char *argv[]) pg_attribute_noreturn(); static void pgarch_exit(SIGNAL_ARGS); static void ArchSigHupHandler(SIGNAL_ARGS); static void ArchSigTermHandler(SIGNAL_ARGS); @@ -114,75 +112,6 @@ static void pgarch_archiveDone(char *xlog); * ------------------------------------------------------------ */ -/* - * pgarch_start - * - * Called from postmaster at startup or after an existing archiver - * died. Attempt to fire up a fresh archiver process. - * - * Returns PID of child process, or 0 if fail. - * - * Note: if fail, we will be called again from the postmaster main loop. - */ -int -pgarch_start(void) -{ - time_t curtime; - pid_t pgArchPid; - - /* - * Do nothing if no archiver needed - */ - if (!XLogArchivingActive()) - return 0; - - /* - * Do nothing if too soon since last archiver start. This is a safety - * valve to protect against continuous respawn attempts if the archiver is - * dying immediately at launch. Note that since we will be re-called from - * the postmaster main loop, we will get another chance later. - */ - curtime = time(NULL); - if ((unsigned int) (curtime - last_pgarch_start_time) < - (unsigned int) PGARCH_RESTART_INTERVAL) - return 0; - last_pgarch_start_time = curtime; - -#ifdef EXEC_BACKEND - switch ((pgArchPid = pgarch_forkexec())) -#else - switch ((pgArchPid = fork_process())) -#endif - { - case -1: - ereport(LOG, - (errmsg("could not fork archiver: %m"))); - return 0; - -#ifndef EXEC_BACKEND - case 0: - /* in postmaster child ... */ - InitPostmasterChild(); - - /* Close the postmaster's sockets */ - ClosePostmasterPorts(false); - - /* Drop our connection to postmaster's shared memory, as well */ - dsm_detach_all(); - PGSharedMemoryDetach(); - - PgArchiverMain(0, NULL); - break; -#endif - - default: - return (int) pgArchPid; - } - - /* shouldn't get here */ - return 0; -} - /* ------------------------------------------------------------ * Local functions called by archiver follow * ------------------------------------------------------------ @@ -222,8 +151,8 @@ pgarch_forkexec(void) * The argc/argv parameters are valid only in EXEC_BACKEND case. However, * since we don't use 'em, it hardly matters... */ -NON_EXEC_STATIC void -PgArchiverMain(int argc, char *argv[]) +void +PgArchiverMain(void) { /* * Ignore all signals usually bound to some action in the postmaster, @@ -255,8 +184,27 @@ PgArchiverMain(int argc, char *argv[]) static void pgarch_exit(SIGNAL_ARGS) { - /* SIGQUIT means curl up and die ... */ - exit(1); + PG_SETMASK(&BlockSig); + + /* + * We DO NOT want to run proc_exit() callbacks -- we're here because + * shared memory may be corrupted, so we don't want to try to clean up our + * transaction. Just nail the windows shut and get out of town. Now that + * there's an atexit callback to prevent third-party code from breaking + * things by calling exit() directly, we have to reset the callbacks + * explicitly to make this work as intended. + */ + on_exit_reset(); + + /* + * Note we do exit(2) not exit(0). This is to force the postmaster into a + * system reset cycle if some idiot DBA sends a manual SIGQUIT to a random + * backend. This is necessary precisely because we don't clean up our + * shared memory state. (The "dead man switch" mechanism in pmsignal.c + * should ensure the postmaster sees this as a crash, too, but no harm in + * being doubly sure.) + */ + exit(2); } /* SIGHUP signal handler for archiver process */ diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c index 81c6499251..9e6bce8f6a 100644 --- a/src/backend/postmaster/pgstat.c +++ b/src/backend/postmaster/pgstat.c @@ -2856,6 +2856,9 @@ pgstat_bestart(void) case BgWriterProcess: beentry->st_backendType = B_BG_WRITER; break; + case ArchiverProcess: + beentry->st_backendType = B_ARCHIVER; + break; case CheckpointerProcess: beentry->st_backendType = B_CHECKPOINTER; break; @@ -4120,6 +4123,9 @@ pgstat_get_backend_desc(BackendType backendType) case B_BG_WRITER: backendDesc = "background writer"; break; + case B_ARCHIVER: + backendDesc = "archiver"; + break; case B_CHECKPOINTER: backendDesc = "checkpointer"; break; diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index ccea231e98..a663a62fd5 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -146,7 +146,8 @@ #define BACKEND_TYPE_AUTOVAC 0x0002 /* autovacuum worker process */ #define BACKEND_TYPE_WALSND 0x0004 /* walsender process */ #define BACKEND_TYPE_BGWORKER 0x0008 /* bgworker process */ -#define BACKEND_TYPE_ALL 0x000F /* OR of all the above */ +#define BACKEND_TYPE_ARCHIVER 0x0010 /* archiver process */ +#define BACKEND_TYPE_ALL 0x001F /* OR of all the above */ #define BACKEND_TYPE_WORKER (BACKEND_TYPE_AUTOVAC | BACKEND_TYPE_BGWORKER) @@ -539,6 +540,7 @@ static void ShmemBackendArrayRemove(Backend *bn); #define StartupDataBase() StartChildProcess(StartupProcess) #define StartBackgroundWriter() StartChildProcess(BgWriterProcess) +#define StartArchiver() StartChildProcess(ArchiverProcess) #define StartCheckpointer() StartChildProcess(CheckpointerProcess) #define StartWalWriter() StartChildProcess(WalWriterProcess) #define StartWalReceiver() StartChildProcess(WalReceiverProcess) @@ -1761,7 +1763,7 @@ ServerLoop(void) /* If we have lost the archiver, try to start a new one. */ if (PgArchPID == 0 && PgArchStartupAllowed()) - PgArchPID = pgarch_start(); + PgArchPID = StartArchiver(); /* If we need to signal the autovacuum launcher, do so now */ if (avlauncher_needs_signal) @@ -2924,7 +2926,7 @@ reaper(SIGNAL_ARGS) if (!IsBinaryUpgrade && AutoVacuumingActive() && AutoVacPID == 0) AutoVacPID = StartAutoVacLauncher(); if (PgArchStartupAllowed() && PgArchPID == 0) - PgArchPID = pgarch_start(); + PgArchPID = StartArchiver(); if (PgStatPID == 0) PgStatPID = pgstat_start(); @@ -3069,10 +3071,8 @@ reaper(SIGNAL_ARGS) { PgArchPID = 0; if (!EXIT_STATUS_0(exitstatus)) - LogChildExit(LOG, _("archiver process"), - pid, exitstatus); - if (PgArchStartupAllowed()) - PgArchPID = pgarch_start(); + HandleChildCrash(pid, exitstatus, + _("archiver process")); continue; } @@ -3318,7 +3318,7 @@ CleanupBackend(int pid, /* * HandleChildCrash -- cleanup after failed backend, bgwriter, checkpointer, - * walwriter, autovacuum, or background worker. + * walwriter, autovacuum, archiver or background worker. * * The objectives here are to clean up our local state about the child * process, and to signal all other remaining children to quickdie. @@ -3523,6 +3523,18 @@ HandleChildCrash(int pid, int exitstatus, const char *procname) signal_child(AutoVacPID, (SendStop ? SIGSTOP : SIGQUIT)); } + /* Take care of the archiver too */ + if (pid == PgArchPID) + PgArchPID = 0; + else if (PgArchPID != 0 && take_action) + { + ereport(DEBUG2, + (errmsg_internal("sending %s to process %d", + (SendStop ? "SIGSTOP" : "SIGQUIT"), + (int) PgArchPID))); + signal_child(PgArchPID, (SendStop ? SIGSTOP : SIGQUIT)); + } + /* * Force a power-cycle of the pgarch process too. (This isn't absolutely * necessary, but it seems like a good idea for robustness, and it @@ -3799,6 +3811,7 @@ PostmasterStateMachine(void) Assert(CheckpointerPID == 0); Assert(WalWriterPID == 0); Assert(AutoVacPID == 0); + Assert(PgArchPID == 0); /* syslogger is not considered here */ pmState = PM_NO_CHILDREN; } @@ -5068,7 +5081,7 @@ sigusr1_handler(SIGNAL_ARGS) */ Assert(PgArchPID == 0); if (XLogArchivingAlways()) - PgArchPID = pgarch_start(); + PgArchPID = StartArchiver(); /* * If we aren't planning to enter hot standby mode later, treat @@ -5346,6 +5359,10 @@ StartChildProcess(AuxProcType type) ereport(LOG, (errmsg("could not fork background writer process: %m"))); break; + case ArchiverProcess: + ereport(LOG, + (errmsg("could not fork archiver process: %m"))); + break; case CheckpointerProcess: ereport(LOG, (errmsg("could not fork checkpointer process: %m"))); diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index c9e35003a5..63a7653457 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -399,6 +399,7 @@ typedef enum BootstrapProcess, StartupProcess, BgWriterProcess, + ArchiverProcess, CheckpointerProcess, WalWriterProcess, WalReceiverProcess, @@ -411,6 +412,7 @@ extern AuxProcType MyAuxProcType; #define AmBootstrapProcess() (MyAuxProcType == BootstrapProcess) #define AmStartupProcess() (MyAuxProcType == StartupProcess) #define AmBackgroundWriterProcess() (MyAuxProcType == BgWriterProcess) +#define AmArchiverProcess() (MyAuxProcType == ArchiverProcess) #define AmCheckpointerProcess() (MyAuxProcType == CheckpointerProcess) #define AmWalWriterProcess() (MyAuxProcType == WalWriterProcess) #define AmWalReceiverProcess() (MyAuxProcType == WalReceiverProcess) diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 88a75fb798..471877d2df 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -706,6 +706,7 @@ typedef enum BackendType B_BACKEND, B_BG_WORKER, B_BG_WRITER, + B_ARCHIVER, B_CHECKPOINTER, B_STARTUP, B_WAL_RECEIVER, diff --git a/src/include/postmaster/pgarch.h b/src/include/postmaster/pgarch.h index 2474eac26a..88f16863d4 100644 --- a/src/include/postmaster/pgarch.h +++ b/src/include/postmaster/pgarch.h @@ -32,8 +32,6 @@ */ extern int pgarch_start(void); -#ifdef EXEC_BACKEND -extern void PgArchiverMain(int argc, char *argv[]) pg_attribute_noreturn(); -#endif +extern void PgArchiverMain(void) pg_attribute_noreturn(); #endif /* _PGARCH_H */ -- 2.16.3 ----Next_Part(Fri_Feb_15_17_29_00_2019_199)-- Content-Type: Text/X-Patch; charset=us-ascii Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="v13-0004-Shared-memory-based-stats-collector.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v22 3/5] Make archiver process an auxiliary process @ 2018-11-07 07:53 Kyotaro Horiguchi <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Kyotaro Horiguchi @ 2018-11-07 07:53 UTC (permalink / raw) This is a preliminary patch for shared-memory based stats collector. Archiver process must be a auxiliary process since it uses shared memory after stats data wes moved onto shared-memory. Make the process an auxiliary process in order to make it work. --- src/backend/bootstrap/bootstrap.c | 8 +++ src/backend/postmaster/pgarch.c | 98 +++++++++---------------------------- src/backend/postmaster/pgstat.c | 6 +++ src/backend/postmaster/postmaster.c | 35 +++++++++---- src/include/miscadmin.h | 2 + src/include/pgstat.h | 1 + src/include/postmaster/pgarch.h | 4 +- 7 files changed, 67 insertions(+), 87 deletions(-) diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c index 9238fbe98d..dde2485b14 100644 --- a/src/backend/bootstrap/bootstrap.c +++ b/src/backend/bootstrap/bootstrap.c @@ -329,6 +329,9 @@ AuxiliaryProcessMain(int argc, char *argv[]) case BgWriterProcess: statmsg = pgstat_get_backend_desc(B_BG_WRITER); break; + case ArchiverProcess: + statmsg = pgstat_get_backend_desc(B_ARCHIVER); + break; case CheckpointerProcess: statmsg = pgstat_get_backend_desc(B_CHECKPOINTER); break; @@ -456,6 +459,11 @@ AuxiliaryProcessMain(int argc, char *argv[]) BackgroundWriterMain(); proc_exit(1); /* should never return */ + case ArchiverProcess: + /* don't set signals, archiver has its own agenda */ + PgArchiverMain(); + proc_exit(1); /* should never return */ + case CheckpointerProcess: /* don't set signals, checkpointer has its own agenda */ CheckpointerMain(); diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c index f84f882c4c..4342ebdab4 100644 --- a/src/backend/postmaster/pgarch.c +++ b/src/backend/postmaster/pgarch.c @@ -77,7 +77,6 @@ * Local data * ---------- */ -static time_t last_pgarch_start_time; static time_t last_sigterm_time = 0; /* @@ -96,7 +95,6 @@ static volatile sig_atomic_t ready_to_stop = false; static pid_t pgarch_forkexec(void); #endif -NON_EXEC_STATIC void PgArchiverMain(int argc, char *argv[]) pg_attribute_noreturn(); static void pgarch_exit(SIGNAL_ARGS); static void ArchSigHupHandler(SIGNAL_ARGS); static void ArchSigTermHandler(SIGNAL_ARGS); @@ -114,75 +112,6 @@ static void pgarch_archiveDone(char *xlog); * ------------------------------------------------------------ */ -/* - * pgarch_start - * - * Called from postmaster at startup or after an existing archiver - * died. Attempt to fire up a fresh archiver process. - * - * Returns PID of child process, or 0 if fail. - * - * Note: if fail, we will be called again from the postmaster main loop. - */ -int -pgarch_start(void) -{ - time_t curtime; - pid_t pgArchPid; - - /* - * Do nothing if no archiver needed - */ - if (!XLogArchivingActive()) - return 0; - - /* - * Do nothing if too soon since last archiver start. This is a safety - * valve to protect against continuous respawn attempts if the archiver is - * dying immediately at launch. Note that since we will be re-called from - * the postmaster main loop, we will get another chance later. - */ - curtime = time(NULL); - if ((unsigned int) (curtime - last_pgarch_start_time) < - (unsigned int) PGARCH_RESTART_INTERVAL) - return 0; - last_pgarch_start_time = curtime; - -#ifdef EXEC_BACKEND - switch ((pgArchPid = pgarch_forkexec())) -#else - switch ((pgArchPid = fork_process())) -#endif - { - case -1: - ereport(LOG, - (errmsg("could not fork archiver: %m"))); - return 0; - -#ifndef EXEC_BACKEND - case 0: - /* in postmaster child ... */ - InitPostmasterChild(); - - /* Close the postmaster's sockets */ - ClosePostmasterPorts(false); - - /* Drop our connection to postmaster's shared memory, as well */ - dsm_detach_all(); - PGSharedMemoryDetach(); - - PgArchiverMain(0, NULL); - break; -#endif - - default: - return (int) pgArchPid; - } - - /* shouldn't get here */ - return 0; -} - /* ------------------------------------------------------------ * Local functions called by archiver follow * ------------------------------------------------------------ @@ -222,8 +151,8 @@ pgarch_forkexec(void) * The argc/argv parameters are valid only in EXEC_BACKEND case. However, * since we don't use 'em, it hardly matters... */ -NON_EXEC_STATIC void -PgArchiverMain(int argc, char *argv[]) +void +PgArchiverMain(void) { /* * Ignore all signals usually bound to some action in the postmaster, @@ -255,8 +184,27 @@ PgArchiverMain(int argc, char *argv[]) static void pgarch_exit(SIGNAL_ARGS) { - /* SIGQUIT means curl up and die ... */ - exit(1); + PG_SETMASK(&BlockSig); + + /* + * We DO NOT want to run proc_exit() callbacks -- we're here because + * shared memory may be corrupted, so we don't want to try to clean up our + * transaction. Just nail the windows shut and get out of town. Now that + * there's an atexit callback to prevent third-party code from breaking + * things by calling exit() directly, we have to reset the callbacks + * explicitly to make this work as intended. + */ + on_exit_reset(); + + /* + * Note we do exit(2) not exit(0). This is to force the postmaster into a + * system reset cycle if some idiot DBA sends a manual SIGQUIT to a random + * backend. This is necessary precisely because we don't clean up our + * shared memory state. (The "dead man switch" mechanism in pmsignal.c + * should ensure the postmaster sees this as a crash, too, but no harm in + * being doubly sure.) + */ + exit(2); } /* SIGHUP signal handler for archiver process */ diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c index 011076c3e3..043e3ff9d2 100644 --- a/src/backend/postmaster/pgstat.c +++ b/src/backend/postmaster/pgstat.c @@ -2934,6 +2934,9 @@ pgstat_bestart(void) case StartupProcess: lbeentry.st_backendType = B_STARTUP; break; + case ArchiverProcess: + beentry->st_backendType = B_ARCHIVER; + break; case BgWriterProcess: lbeentry.st_backendType = B_BG_WRITER; break; @@ -4277,6 +4280,9 @@ pgstat_get_backend_desc(BackendType backendType) switch (backendType) { + case B_ARCHIVER: + backendDesc = "archiver"; + break; case B_AUTOVAC_LAUNCHER: backendDesc = "autovacuum launcher"; break; diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index a5446d54bb..582434252f 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -146,7 +146,8 @@ #define BACKEND_TYPE_AUTOVAC 0x0002 /* autovacuum worker process */ #define BACKEND_TYPE_WALSND 0x0004 /* walsender process */ #define BACKEND_TYPE_BGWORKER 0x0008 /* bgworker process */ -#define BACKEND_TYPE_ALL 0x000F /* OR of all the above */ +#define BACKEND_TYPE_ARCHIVER 0x0010 /* archiver process */ +#define BACKEND_TYPE_ALL 0x001F /* OR of all the above */ #define BACKEND_TYPE_WORKER (BACKEND_TYPE_AUTOVAC | BACKEND_TYPE_BGWORKER) @@ -539,6 +540,7 @@ static void ShmemBackendArrayRemove(Backend *bn); #endif /* EXEC_BACKEND */ #define StartupDataBase() StartChildProcess(StartupProcess) +#define StartArchiver() StartChildProcess(ArchiverProcess) #define StartBackgroundWriter() StartChildProcess(BgWriterProcess) #define StartCheckpointer() StartChildProcess(CheckpointerProcess) #define StartWalWriter() StartChildProcess(WalWriterProcess) @@ -1762,7 +1764,7 @@ ServerLoop(void) /* If we have lost the archiver, try to start a new one. */ if (PgArchPID == 0 && PgArchStartupAllowed()) - PgArchPID = pgarch_start(); + PgArchPID = StartArchiver(); /* If we need to signal the autovacuum launcher, do so now */ if (avlauncher_needs_signal) @@ -2991,7 +2993,7 @@ reaper(SIGNAL_ARGS) if (!IsBinaryUpgrade && AutoVacuumingActive() && AutoVacPID == 0) AutoVacPID = StartAutoVacLauncher(); if (PgArchStartupAllowed() && PgArchPID == 0) - PgArchPID = pgarch_start(); + PgArchPID = StartArchiver(); if (PgStatPID == 0) PgStatPID = pgstat_start(); @@ -3136,10 +3138,8 @@ reaper(SIGNAL_ARGS) { PgArchPID = 0; if (!EXIT_STATUS_0(exitstatus)) - LogChildExit(LOG, _("archiver process"), - pid, exitstatus); - if (PgArchStartupAllowed()) - PgArchPID = pgarch_start(); + HandleChildCrash(pid, exitstatus, + _("archiver process")); continue; } @@ -3385,7 +3385,7 @@ CleanupBackend(int pid, /* * HandleChildCrash -- cleanup after failed backend, bgwriter, checkpointer, - * walwriter, autovacuum, or background worker. + * walwriter, autovacuum, archiver or background worker. * * The objectives here are to clean up our local state about the child * process, and to signal all other remaining children to quickdie. @@ -3590,6 +3590,18 @@ HandleChildCrash(int pid, int exitstatus, const char *procname) signal_child(AutoVacPID, (SendStop ? SIGSTOP : SIGQUIT)); } + /* Take care of the archiver too */ + if (pid == PgArchPID) + PgArchPID = 0; + else if (PgArchPID != 0 && take_action) + { + ereport(DEBUG2, + (errmsg_internal("sending %s to process %d", + (SendStop ? "SIGSTOP" : "SIGQUIT"), + (int) PgArchPID))); + signal_child(PgArchPID, (SendStop ? SIGSTOP : SIGQUIT)); + } + /* * Force a power-cycle of the pgarch process too. (This isn't absolutely * necessary, but it seems like a good idea for robustness, and it @@ -3862,6 +3874,7 @@ PostmasterStateMachine(void) Assert(CheckpointerPID == 0); Assert(WalWriterPID == 0); Assert(AutoVacPID == 0); + Assert(PgArchPID == 0); /* syslogger is not considered here */ pmState = PM_NO_CHILDREN; } @@ -5131,7 +5144,7 @@ sigusr1_handler(SIGNAL_ARGS) */ Assert(PgArchPID == 0); if (XLogArchivingAlways()) - PgArchPID = pgarch_start(); + PgArchPID = StartArchiver(); /* * If we aren't planning to enter hot standby mode later, treat @@ -5414,6 +5427,10 @@ StartChildProcess(AuxProcType type) ereport(LOG, (errmsg("could not fork startup process: %m"))); break; + case ArchiverProcess: + ereport(LOG, + (errmsg("could not fork archiver process: %m"))); + break; case BgWriterProcess: ereport(LOG, (errmsg("could not fork background writer process: %m"))); diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index bc6e03fbc7..1f4db67f3f 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -399,6 +399,7 @@ typedef enum BootstrapProcess, StartupProcess, BgWriterProcess, + ArchiverProcess, CheckpointerProcess, WalWriterProcess, WalReceiverProcess, @@ -411,6 +412,7 @@ extern AuxProcType MyAuxProcType; #define AmBootstrapProcess() (MyAuxProcType == BootstrapProcess) #define AmStartupProcess() (MyAuxProcType == StartupProcess) #define AmBackgroundWriterProcess() (MyAuxProcType == BgWriterProcess) +#define AmArchiverProcess() (MyAuxProcType == ArchiverProcess) #define AmCheckpointerProcess() (MyAuxProcType == CheckpointerProcess) #define AmWalWriterProcess() (MyAuxProcType == WalWriterProcess) #define AmWalReceiverProcess() (MyAuxProcType == WalReceiverProcess) diff --git a/src/include/pgstat.h b/src/include/pgstat.h index fe076d823d..65713abc2b 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -718,6 +718,7 @@ typedef struct PgStat_GlobalStats */ typedef enum BackendType { + B_ARCHIVER, B_AUTOVAC_LAUNCHER, B_AUTOVAC_WORKER, B_BACKEND, diff --git a/src/include/postmaster/pgarch.h b/src/include/postmaster/pgarch.h index 2474eac26a..88f16863d4 100644 --- a/src/include/postmaster/pgarch.h +++ b/src/include/postmaster/pgarch.h @@ -32,8 +32,6 @@ */ extern int pgarch_start(void); -#ifdef EXEC_BACKEND -extern void PgArchiverMain(int argc, char *argv[]) pg_attribute_noreturn(); -#endif +extern void PgArchiverMain(void) pg_attribute_noreturn(); #endif /* _PGARCH_H */ -- 2.16.3 ----Next_Part(Tue_Sep_10_17_58_58_2019_765)-- Content-Type: Text/X-Patch; charset=us-ascii Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="v22-0004-Shared-memory-based-stats-collector.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH 3/6] Make archiver process an auxiliary process @ 2018-11-07 07:53 Kyotaro Horiguchi <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Kyotaro Horiguchi @ 2018-11-07 07:53 UTC (permalink / raw) This is a preliminary patch for shared-memory based stats collector. Archiver process must be a auxiliary process since it uses shared memory after stats data wes moved onto shared-memory. Make the process an auxiliary process in order to make it work. --- src/backend/bootstrap/bootstrap.c | 8 +++ src/backend/postmaster/pgarch.c | 98 +++++++++---------------------------- src/backend/postmaster/pgstat.c | 6 +++ src/backend/postmaster/postmaster.c | 35 +++++++++---- src/include/miscadmin.h | 2 + src/include/pgstat.h | 1 + src/include/postmaster/pgarch.h | 4 +- 7 files changed, 67 insertions(+), 87 deletions(-) diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c index 4d7ed8ad1a..b0878a3dd9 100644 --- a/src/backend/bootstrap/bootstrap.c +++ b/src/backend/bootstrap/bootstrap.c @@ -328,6 +328,9 @@ AuxiliaryProcessMain(int argc, char *argv[]) case BgWriterProcess: statmsg = pgstat_get_backend_desc(B_BG_WRITER); break; + case ArchiverProcess: + statmsg = pgstat_get_backend_desc(B_ARCHIVER); + break; case CheckpointerProcess: statmsg = pgstat_get_backend_desc(B_CHECKPOINTER); break; @@ -455,6 +458,11 @@ AuxiliaryProcessMain(int argc, char *argv[]) BackgroundWriterMain(); proc_exit(1); /* should never return */ + case ArchiverProcess: + /* don't set signals, archiver has its own agenda */ + PgArchiverMain(); + proc_exit(1); /* should never return */ + case CheckpointerProcess: /* don't set signals, checkpointer has its own agenda */ CheckpointerMain(); diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c index f84f882c4c..4342ebdab4 100644 --- a/src/backend/postmaster/pgarch.c +++ b/src/backend/postmaster/pgarch.c @@ -77,7 +77,6 @@ * Local data * ---------- */ -static time_t last_pgarch_start_time; static time_t last_sigterm_time = 0; /* @@ -96,7 +95,6 @@ static volatile sig_atomic_t ready_to_stop = false; static pid_t pgarch_forkexec(void); #endif -NON_EXEC_STATIC void PgArchiverMain(int argc, char *argv[]) pg_attribute_noreturn(); static void pgarch_exit(SIGNAL_ARGS); static void ArchSigHupHandler(SIGNAL_ARGS); static void ArchSigTermHandler(SIGNAL_ARGS); @@ -114,75 +112,6 @@ static void pgarch_archiveDone(char *xlog); * ------------------------------------------------------------ */ -/* - * pgarch_start - * - * Called from postmaster at startup or after an existing archiver - * died. Attempt to fire up a fresh archiver process. - * - * Returns PID of child process, or 0 if fail. - * - * Note: if fail, we will be called again from the postmaster main loop. - */ -int -pgarch_start(void) -{ - time_t curtime; - pid_t pgArchPid; - - /* - * Do nothing if no archiver needed - */ - if (!XLogArchivingActive()) - return 0; - - /* - * Do nothing if too soon since last archiver start. This is a safety - * valve to protect against continuous respawn attempts if the archiver is - * dying immediately at launch. Note that since we will be re-called from - * the postmaster main loop, we will get another chance later. - */ - curtime = time(NULL); - if ((unsigned int) (curtime - last_pgarch_start_time) < - (unsigned int) PGARCH_RESTART_INTERVAL) - return 0; - last_pgarch_start_time = curtime; - -#ifdef EXEC_BACKEND - switch ((pgArchPid = pgarch_forkexec())) -#else - switch ((pgArchPid = fork_process())) -#endif - { - case -1: - ereport(LOG, - (errmsg("could not fork archiver: %m"))); - return 0; - -#ifndef EXEC_BACKEND - case 0: - /* in postmaster child ... */ - InitPostmasterChild(); - - /* Close the postmaster's sockets */ - ClosePostmasterPorts(false); - - /* Drop our connection to postmaster's shared memory, as well */ - dsm_detach_all(); - PGSharedMemoryDetach(); - - PgArchiverMain(0, NULL); - break; -#endif - - default: - return (int) pgArchPid; - } - - /* shouldn't get here */ - return 0; -} - /* ------------------------------------------------------------ * Local functions called by archiver follow * ------------------------------------------------------------ @@ -222,8 +151,8 @@ pgarch_forkexec(void) * The argc/argv parameters are valid only in EXEC_BACKEND case. However, * since we don't use 'em, it hardly matters... */ -NON_EXEC_STATIC void -PgArchiverMain(int argc, char *argv[]) +void +PgArchiverMain(void) { /* * Ignore all signals usually bound to some action in the postmaster, @@ -255,8 +184,27 @@ PgArchiverMain(int argc, char *argv[]) static void pgarch_exit(SIGNAL_ARGS) { - /* SIGQUIT means curl up and die ... */ - exit(1); + PG_SETMASK(&BlockSig); + + /* + * We DO NOT want to run proc_exit() callbacks -- we're here because + * shared memory may be corrupted, so we don't want to try to clean up our + * transaction. Just nail the windows shut and get out of town. Now that + * there's an atexit callback to prevent third-party code from breaking + * things by calling exit() directly, we have to reset the callbacks + * explicitly to make this work as intended. + */ + on_exit_reset(); + + /* + * Note we do exit(2) not exit(0). This is to force the postmaster into a + * system reset cycle if some idiot DBA sends a manual SIGQUIT to a random + * backend. This is necessary precisely because we don't clean up our + * shared memory state. (The "dead man switch" mechanism in pmsignal.c + * should ensure the postmaster sees this as a crash, too, but no harm in + * being doubly sure.) + */ + exit(2); } /* SIGHUP signal handler for archiver process */ diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c index 81c6499251..8d3c45dd4e 100644 --- a/src/backend/postmaster/pgstat.c +++ b/src/backend/postmaster/pgstat.c @@ -2856,6 +2856,9 @@ pgstat_bestart(void) case BgWriterProcess: beentry->st_backendType = B_BG_WRITER; break; + case ArchiverProcess: + beentry->st_backendType = B_ARCHIVER; + break; case CheckpointerProcess: beentry->st_backendType = B_CHECKPOINTER; break; @@ -4105,6 +4108,9 @@ pgstat_get_backend_desc(BackendType backendType) switch (backendType) { + case B_ARCHIVER: + backendDesc = "archiver"; + break; case B_AUTOVAC_LAUNCHER: backendDesc = "autovacuum launcher"; break; diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index ccea231e98..820f356038 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -146,7 +146,8 @@ #define BACKEND_TYPE_AUTOVAC 0x0002 /* autovacuum worker process */ #define BACKEND_TYPE_WALSND 0x0004 /* walsender process */ #define BACKEND_TYPE_BGWORKER 0x0008 /* bgworker process */ -#define BACKEND_TYPE_ALL 0x000F /* OR of all the above */ +#define BACKEND_TYPE_ARCHIVER 0x0010 /* archiver process */ +#define BACKEND_TYPE_ALL 0x001F /* OR of all the above */ #define BACKEND_TYPE_WORKER (BACKEND_TYPE_AUTOVAC | BACKEND_TYPE_BGWORKER) @@ -538,6 +539,7 @@ static void ShmemBackendArrayRemove(Backend *bn); #endif /* EXEC_BACKEND */ #define StartupDataBase() StartChildProcess(StartupProcess) +#define StartArchiver() StartChildProcess(ArchiverProcess) #define StartBackgroundWriter() StartChildProcess(BgWriterProcess) #define StartCheckpointer() StartChildProcess(CheckpointerProcess) #define StartWalWriter() StartChildProcess(WalWriterProcess) @@ -1761,7 +1763,7 @@ ServerLoop(void) /* If we have lost the archiver, try to start a new one. */ if (PgArchPID == 0 && PgArchStartupAllowed()) - PgArchPID = pgarch_start(); + PgArchPID = StartArchiver(); /* If we need to signal the autovacuum launcher, do so now */ if (avlauncher_needs_signal) @@ -2924,7 +2926,7 @@ reaper(SIGNAL_ARGS) if (!IsBinaryUpgrade && AutoVacuumingActive() && AutoVacPID == 0) AutoVacPID = StartAutoVacLauncher(); if (PgArchStartupAllowed() && PgArchPID == 0) - PgArchPID = pgarch_start(); + PgArchPID = StartArchiver(); if (PgStatPID == 0) PgStatPID = pgstat_start(); @@ -3069,10 +3071,8 @@ reaper(SIGNAL_ARGS) { PgArchPID = 0; if (!EXIT_STATUS_0(exitstatus)) - LogChildExit(LOG, _("archiver process"), - pid, exitstatus); - if (PgArchStartupAllowed()) - PgArchPID = pgarch_start(); + HandleChildCrash(pid, exitstatus, + _("archiver process")); continue; } @@ -3318,7 +3318,7 @@ CleanupBackend(int pid, /* * HandleChildCrash -- cleanup after failed backend, bgwriter, checkpointer, - * walwriter, autovacuum, or background worker. + * walwriter, autovacuum, archiver or background worker. * * The objectives here are to clean up our local state about the child * process, and to signal all other remaining children to quickdie. @@ -3523,6 +3523,18 @@ HandleChildCrash(int pid, int exitstatus, const char *procname) signal_child(AutoVacPID, (SendStop ? SIGSTOP : SIGQUIT)); } + /* Take care of the archiver too */ + if (pid == PgArchPID) + PgArchPID = 0; + else if (PgArchPID != 0 && take_action) + { + ereport(DEBUG2, + (errmsg_internal("sending %s to process %d", + (SendStop ? "SIGSTOP" : "SIGQUIT"), + (int) PgArchPID))); + signal_child(PgArchPID, (SendStop ? SIGSTOP : SIGQUIT)); + } + /* * Force a power-cycle of the pgarch process too. (This isn't absolutely * necessary, but it seems like a good idea for robustness, and it @@ -3799,6 +3811,7 @@ PostmasterStateMachine(void) Assert(CheckpointerPID == 0); Assert(WalWriterPID == 0); Assert(AutoVacPID == 0); + Assert(PgArchPID == 0); /* syslogger is not considered here */ pmState = PM_NO_CHILDREN; } @@ -5068,7 +5081,7 @@ sigusr1_handler(SIGNAL_ARGS) */ Assert(PgArchPID == 0); if (XLogArchivingAlways()) - PgArchPID = pgarch_start(); + PgArchPID = StartArchiver(); /* * If we aren't planning to enter hot standby mode later, treat @@ -5342,6 +5355,10 @@ StartChildProcess(AuxProcType type) ereport(LOG, (errmsg("could not fork startup process: %m"))); break; + case ArchiverProcess: + ereport(LOG, + (errmsg("could not fork archiver process: %m"))); + break; case BgWriterProcess: ereport(LOG, (errmsg("could not fork background writer process: %m"))); diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index c9e35003a5..63a7653457 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -399,6 +399,7 @@ typedef enum BootstrapProcess, StartupProcess, BgWriterProcess, + ArchiverProcess, CheckpointerProcess, WalWriterProcess, WalReceiverProcess, @@ -411,6 +412,7 @@ extern AuxProcType MyAuxProcType; #define AmBootstrapProcess() (MyAuxProcType == BootstrapProcess) #define AmStartupProcess() (MyAuxProcType == StartupProcess) #define AmBackgroundWriterProcess() (MyAuxProcType == BgWriterProcess) +#define AmArchiverProcess() (MyAuxProcType == ArchiverProcess) #define AmCheckpointerProcess() (MyAuxProcType == CheckpointerProcess) #define AmWalWriterProcess() (MyAuxProcType == WalWriterProcess) #define AmWalReceiverProcess() (MyAuxProcType == WalReceiverProcess) diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 88a75fb798..3324be8a81 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -701,6 +701,7 @@ typedef struct PgStat_GlobalStats */ typedef enum BackendType { + B_ARCHIVER, B_AUTOVAC_LAUNCHER, B_AUTOVAC_WORKER, B_BACKEND, diff --git a/src/include/postmaster/pgarch.h b/src/include/postmaster/pgarch.h index 2474eac26a..88f16863d4 100644 --- a/src/include/postmaster/pgarch.h +++ b/src/include/postmaster/pgarch.h @@ -32,8 +32,6 @@ */ extern int pgarch_start(void); -#ifdef EXEC_BACKEND -extern void PgArchiverMain(int argc, char *argv[]) pg_attribute_noreturn(); -#endif +extern void PgArchiverMain(void) pg_attribute_noreturn(); #endif /* _PGARCH_H */ -- 2.16.3 ----Next_Part(Thu_Feb_21_16_05_55_2019_560)-- Content-Type: Text/X-Patch; charset=us-ascii Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="v16-0004-Allow-dsm-to-use-on-postmaster.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH 3/5] Make archiver process an auxiliary process @ 2018-11-07 07:53 Kyotaro Horiguchi <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Kyotaro Horiguchi @ 2018-11-07 07:53 UTC (permalink / raw) This is a preliminary patch for shared-memory based stats collector. Archiver process must be a auxiliary process since it uses shared memory after stats data wes moved onto shared-memory. Make the process an auxiliary process in order to make it work. --- src/backend/bootstrap/bootstrap.c | 8 +++ src/backend/postmaster/pgarch.c | 98 +++++++++---------------------------- src/backend/postmaster/pgstat.c | 6 +++ src/backend/postmaster/postmaster.c | 35 +++++++++---- src/include/miscadmin.h | 2 + src/include/pgstat.h | 1 + src/include/postmaster/pgarch.h | 4 +- 7 files changed, 67 insertions(+), 87 deletions(-) diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c index 43627ab8f4..7872a2d9d7 100644 --- a/src/backend/bootstrap/bootstrap.c +++ b/src/backend/bootstrap/bootstrap.c @@ -329,6 +329,9 @@ AuxiliaryProcessMain(int argc, char *argv[]) case BgWriterProcess: statmsg = pgstat_get_backend_desc(B_BG_WRITER); break; + case ArchiverProcess: + statmsg = pgstat_get_backend_desc(B_ARCHIVER); + break; case CheckpointerProcess: statmsg = pgstat_get_backend_desc(B_CHECKPOINTER); break; @@ -456,6 +459,11 @@ AuxiliaryProcessMain(int argc, char *argv[]) BackgroundWriterMain(); proc_exit(1); /* should never return */ + case ArchiverProcess: + /* don't set signals, archiver has its own agenda */ + PgArchiverMain(); + proc_exit(1); /* should never return */ + case CheckpointerProcess: /* don't set signals, checkpointer has its own agenda */ CheckpointerMain(); diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c index f84f882c4c..4342ebdab4 100644 --- a/src/backend/postmaster/pgarch.c +++ b/src/backend/postmaster/pgarch.c @@ -77,7 +77,6 @@ * Local data * ---------- */ -static time_t last_pgarch_start_time; static time_t last_sigterm_time = 0; /* @@ -96,7 +95,6 @@ static volatile sig_atomic_t ready_to_stop = false; static pid_t pgarch_forkexec(void); #endif -NON_EXEC_STATIC void PgArchiverMain(int argc, char *argv[]) pg_attribute_noreturn(); static void pgarch_exit(SIGNAL_ARGS); static void ArchSigHupHandler(SIGNAL_ARGS); static void ArchSigTermHandler(SIGNAL_ARGS); @@ -114,75 +112,6 @@ static void pgarch_archiveDone(char *xlog); * ------------------------------------------------------------ */ -/* - * pgarch_start - * - * Called from postmaster at startup or after an existing archiver - * died. Attempt to fire up a fresh archiver process. - * - * Returns PID of child process, or 0 if fail. - * - * Note: if fail, we will be called again from the postmaster main loop. - */ -int -pgarch_start(void) -{ - time_t curtime; - pid_t pgArchPid; - - /* - * Do nothing if no archiver needed - */ - if (!XLogArchivingActive()) - return 0; - - /* - * Do nothing if too soon since last archiver start. This is a safety - * valve to protect against continuous respawn attempts if the archiver is - * dying immediately at launch. Note that since we will be re-called from - * the postmaster main loop, we will get another chance later. - */ - curtime = time(NULL); - if ((unsigned int) (curtime - last_pgarch_start_time) < - (unsigned int) PGARCH_RESTART_INTERVAL) - return 0; - last_pgarch_start_time = curtime; - -#ifdef EXEC_BACKEND - switch ((pgArchPid = pgarch_forkexec())) -#else - switch ((pgArchPid = fork_process())) -#endif - { - case -1: - ereport(LOG, - (errmsg("could not fork archiver: %m"))); - return 0; - -#ifndef EXEC_BACKEND - case 0: - /* in postmaster child ... */ - InitPostmasterChild(); - - /* Close the postmaster's sockets */ - ClosePostmasterPorts(false); - - /* Drop our connection to postmaster's shared memory, as well */ - dsm_detach_all(); - PGSharedMemoryDetach(); - - PgArchiverMain(0, NULL); - break; -#endif - - default: - return (int) pgArchPid; - } - - /* shouldn't get here */ - return 0; -} - /* ------------------------------------------------------------ * Local functions called by archiver follow * ------------------------------------------------------------ @@ -222,8 +151,8 @@ pgarch_forkexec(void) * The argc/argv parameters are valid only in EXEC_BACKEND case. However, * since we don't use 'em, it hardly matters... */ -NON_EXEC_STATIC void -PgArchiverMain(int argc, char *argv[]) +void +PgArchiverMain(void) { /* * Ignore all signals usually bound to some action in the postmaster, @@ -255,8 +184,27 @@ PgArchiverMain(int argc, char *argv[]) static void pgarch_exit(SIGNAL_ARGS) { - /* SIGQUIT means curl up and die ... */ - exit(1); + PG_SETMASK(&BlockSig); + + /* + * We DO NOT want to run proc_exit() callbacks -- we're here because + * shared memory may be corrupted, so we don't want to try to clean up our + * transaction. Just nail the windows shut and get out of town. Now that + * there's an atexit callback to prevent third-party code from breaking + * things by calling exit() directly, we have to reset the callbacks + * explicitly to make this work as intended. + */ + on_exit_reset(); + + /* + * Note we do exit(2) not exit(0). This is to force the postmaster into a + * system reset cycle if some idiot DBA sends a manual SIGQUIT to a random + * backend. This is necessary precisely because we don't clean up our + * shared memory state. (The "dead man switch" mechanism in pmsignal.c + * should ensure the postmaster sees this as a crash, too, but no harm in + * being doubly sure.) + */ + exit(2); } /* SIGHUP signal handler for archiver process */ diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c index b4f2b28b51..f4ec142cab 100644 --- a/src/backend/postmaster/pgstat.c +++ b/src/backend/postmaster/pgstat.c @@ -2934,6 +2934,9 @@ pgstat_bestart(void) case StartupProcess: lbeentry.st_backendType = B_STARTUP; break; + case ArchiverProcess: + beentry->st_backendType = B_ARCHIVER; + break; case BgWriterProcess: lbeentry.st_backendType = B_BG_WRITER; break; @@ -4277,6 +4280,9 @@ pgstat_get_backend_desc(BackendType backendType) switch (backendType) { + case B_ARCHIVER: + backendDesc = "archiver"; + break; case B_AUTOVAC_LAUNCHER: backendDesc = "autovacuum launcher"; break; diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index 688ad439ed..4574ebf2de 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -146,7 +146,8 @@ #define BACKEND_TYPE_AUTOVAC 0x0002 /* autovacuum worker process */ #define BACKEND_TYPE_WALSND 0x0004 /* walsender process */ #define BACKEND_TYPE_BGWORKER 0x0008 /* bgworker process */ -#define BACKEND_TYPE_ALL 0x000F /* OR of all the above */ +#define BACKEND_TYPE_ARCHIVER 0x0010 /* archiver process */ +#define BACKEND_TYPE_ALL 0x001F /* OR of all the above */ #define BACKEND_TYPE_WORKER (BACKEND_TYPE_AUTOVAC | BACKEND_TYPE_BGWORKER) @@ -539,6 +540,7 @@ static void ShmemBackendArrayRemove(Backend *bn); #endif /* EXEC_BACKEND */ #define StartupDataBase() StartChildProcess(StartupProcess) +#define StartArchiver() StartChildProcess(ArchiverProcess) #define StartBackgroundWriter() StartChildProcess(BgWriterProcess) #define StartCheckpointer() StartChildProcess(CheckpointerProcess) #define StartWalWriter() StartChildProcess(WalWriterProcess) @@ -1762,7 +1764,7 @@ ServerLoop(void) /* If we have lost the archiver, try to start a new one. */ if (PgArchPID == 0 && PgArchStartupAllowed()) - PgArchPID = pgarch_start(); + PgArchPID = StartArchiver(); /* If we need to signal the autovacuum launcher, do so now */ if (avlauncher_needs_signal) @@ -2977,7 +2979,7 @@ reaper(SIGNAL_ARGS) if (!IsBinaryUpgrade && AutoVacuumingActive() && AutoVacPID == 0) AutoVacPID = StartAutoVacLauncher(); if (PgArchStartupAllowed() && PgArchPID == 0) - PgArchPID = pgarch_start(); + PgArchPID = StartArchiver(); if (PgStatPID == 0) PgStatPID = pgstat_start(); @@ -3122,10 +3124,8 @@ reaper(SIGNAL_ARGS) { PgArchPID = 0; if (!EXIT_STATUS_0(exitstatus)) - LogChildExit(LOG, _("archiver process"), - pid, exitstatus); - if (PgArchStartupAllowed()) - PgArchPID = pgarch_start(); + HandleChildCrash(pid, exitstatus, + _("archiver process")); continue; } @@ -3371,7 +3371,7 @@ CleanupBackend(int pid, /* * HandleChildCrash -- cleanup after failed backend, bgwriter, checkpointer, - * walwriter, autovacuum, or background worker. + * walwriter, autovacuum, archiver or background worker. * * The objectives here are to clean up our local state about the child * process, and to signal all other remaining children to quickdie. @@ -3576,6 +3576,18 @@ HandleChildCrash(int pid, int exitstatus, const char *procname) signal_child(AutoVacPID, (SendStop ? SIGSTOP : SIGQUIT)); } + /* Take care of the archiver too */ + if (pid == PgArchPID) + PgArchPID = 0; + else if (PgArchPID != 0 && take_action) + { + ereport(DEBUG2, + (errmsg_internal("sending %s to process %d", + (SendStop ? "SIGSTOP" : "SIGQUIT"), + (int) PgArchPID))); + signal_child(PgArchPID, (SendStop ? SIGSTOP : SIGQUIT)); + } + /* * Force a power-cycle of the pgarch process too. (This isn't absolutely * necessary, but it seems like a good idea for robustness, and it @@ -3848,6 +3860,7 @@ PostmasterStateMachine(void) Assert(CheckpointerPID == 0); Assert(WalWriterPID == 0); Assert(AutoVacPID == 0); + Assert(PgArchPID == 0); /* syslogger is not considered here */ pmState = PM_NO_CHILDREN; } @@ -5117,7 +5130,7 @@ sigusr1_handler(SIGNAL_ARGS) */ Assert(PgArchPID == 0); if (XLogArchivingAlways()) - PgArchPID = pgarch_start(); + PgArchPID = StartArchiver(); /* * If we aren't planning to enter hot standby mode later, treat @@ -5400,6 +5413,10 @@ StartChildProcess(AuxProcType type) ereport(LOG, (errmsg("could not fork startup process: %m"))); break; + case ArchiverProcess: + ereport(LOG, + (errmsg("could not fork archiver process: %m"))); + break; case BgWriterProcess: ereport(LOG, (errmsg("could not fork background writer process: %m"))); diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index 61a24c2e3c..0b49b63327 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -399,6 +399,7 @@ typedef enum BootstrapProcess, StartupProcess, BgWriterProcess, + ArchiverProcess, CheckpointerProcess, WalWriterProcess, WalReceiverProcess, @@ -411,6 +412,7 @@ extern AuxProcType MyAuxProcType; #define AmBootstrapProcess() (MyAuxProcType == BootstrapProcess) #define AmStartupProcess() (MyAuxProcType == StartupProcess) #define AmBackgroundWriterProcess() (MyAuxProcType == BgWriterProcess) +#define AmArchiverProcess() (MyAuxProcType == ArchiverProcess) #define AmCheckpointerProcess() (MyAuxProcType == CheckpointerProcess) #define AmWalWriterProcess() (MyAuxProcType == WalWriterProcess) #define AmWalReceiverProcess() (MyAuxProcType == WalReceiverProcess) diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 0a3ad3a188..b3f00e1943 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -719,6 +719,7 @@ typedef struct PgStat_GlobalStats */ typedef enum BackendType { + B_ARCHIVER, B_AUTOVAC_LAUNCHER, B_AUTOVAC_WORKER, B_BACKEND, diff --git a/src/include/postmaster/pgarch.h b/src/include/postmaster/pgarch.h index 2474eac26a..88f16863d4 100644 --- a/src/include/postmaster/pgarch.h +++ b/src/include/postmaster/pgarch.h @@ -32,8 +32,6 @@ */ extern int pgarch_start(void); -#ifdef EXEC_BACKEND -extern void PgArchiverMain(int argc, char *argv[]) pg_attribute_noreturn(); -#endif +extern void PgArchiverMain(void) pg_attribute_noreturn(); #endif /* _PGARCH_H */ -- 2.16.3 ----Next_Part(Thu_Jul_04_19_27_54_2019_888)-- Content-Type: Text/X-Patch; charset=us-ascii Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="0004-Shared-memory-based-stats-collector.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v28 4/7] Make archiver process an auxiliary process @ 2020-03-13 07:59 Kyotaro Horiguchi <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Kyotaro Horiguchi @ 2020-03-13 07:59 UTC (permalink / raw) This is a preliminary patch for shared-memory based stats collector. Archiver process must be a auxiliary process since it uses shared memory after stats data was moved into shared-memory. Make the process an auxiliary process in order to make it work. --- src/backend/access/transam/xlog.c | 46 ++++++++++ src/backend/access/transam/xlogarchive.c | 2 +- src/backend/bootstrap/bootstrap.c | 22 ++--- src/backend/postmaster/pgarch.c | 102 ++++------------------- src/backend/postmaster/postmaster.c | 53 ++++++------ src/include/access/xlog.h | 2 + src/include/access/xlog_internal.h | 1 + src/include/miscadmin.h | 2 + src/include/postmaster/pgarch.h | 4 +- src/include/storage/pmsignal.h | 1 - 10 files changed, 108 insertions(+), 127 deletions(-) diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index 8fe92962b0..5e663699d5 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -681,6 +681,13 @@ typedef struct XLogCtlData */ Latch recoveryWakeupLatch; + /* + * archiverWakeupLatch is used to wake up the archiver process to process + * completed WAL segments, if it is waiting for WAL to arrive. Protected + * by info_lck. + */ + Latch *archiverWakeupLatch; + /* * During recovery, we keep a copy of the latest checkpoint record here. * lastCheckPointRecPtr points to start of checkpoint record and @@ -8386,6 +8393,45 @@ GetLastSegSwitchData(XLogRecPtr *lastSwitchLSN) return result; } +/* + * XLogArchiveWakeupStart - Set up archiver wakeup stuff + */ +void +XLogArchiveWakeupStart(void) +{ + SpinLockAcquire(&XLogCtl->info_lck); + Assert(XLogCtl->archiverWakeupLatch == NULL); + XLogCtl->archiverWakeupLatch = MyLatch; + SpinLockRelease(&XLogCtl->info_lck); +} + +/* + * XLogArchiveWakeupEnd - Clean up archiver wakeup stuff + */ +void +XLogArchiveWakeupEnd(void) +{ + SpinLockAcquire(&XLogCtl->info_lck); + XLogCtl->archiverWakeupLatch = NULL; + SpinLockRelease(&XLogCtl->info_lck); +} + +/* + * XLogWakeupArchiver - Wake up archiver process + */ +void +XLogArchiveWakeup(void) +{ + Latch *latch; + + SpinLockAcquire(&XLogCtl->info_lck); + latch = XLogCtl->archiverWakeupLatch; + SpinLockRelease(&XLogCtl->info_lck); + + if (latch) + SetLatch(latch); +} + /* * This must be called ONCE during postmaster or standalone-backend shutdown */ diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c index 914ad340ea..47c2b4a373 100644 --- a/src/backend/access/transam/xlogarchive.c +++ b/src/backend/access/transam/xlogarchive.c @@ -489,7 +489,7 @@ XLogArchiveNotify(const char *xlog) /* Notify archiver that it's got something to do */ if (IsUnderPostmaster) - SendPostmasterSignal(PMSIGNAL_WAKEN_ARCHIVER); + XLogArchiveWakeup(); } /* diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c index 5480a024e0..d398ce6f03 100644 --- a/src/backend/bootstrap/bootstrap.c +++ b/src/backend/bootstrap/bootstrap.c @@ -319,6 +319,9 @@ AuxiliaryProcessMain(int argc, char *argv[]) case StartupProcess: MyBackendType = B_STARTUP; break; + case ArchiverProcess: + MyBackendType = B_ARCHIVER; + break; case BgWriterProcess: MyBackendType = B_BG_WRITER; break; @@ -439,30 +442,29 @@ AuxiliaryProcessMain(int argc, char *argv[]) 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 */ + proc_exit(1); + + case ArchiverProcess: + PgArchiverMain(); + proc_exit(1); case BgWriterProcess: - /* don't set signals, bgwriter has its own agenda */ BackgroundWriterMain(); - proc_exit(1); /* should never return */ + proc_exit(1); case CheckpointerProcess: - /* don't set signals, checkpointer has its own agenda */ CheckpointerMain(); - proc_exit(1); /* should never return */ + proc_exit(1); case WalWriterProcess: - /* don't set signals, walwriter has its own agenda */ InitXLOGAccess(); WalWriterMain(); - proc_exit(1); /* should never return */ + proc_exit(1); case WalReceiverProcess: - /* don't set signals, walreceiver has its own agenda */ WalReceiverMain(); - proc_exit(1); /* should never return */ + proc_exit(1); default: elog(PANIC, "unrecognized process type: %d", (int) MyAuxProcType); diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c index 37be0e2bbb..6fe7a136ba 100644 --- a/src/backend/postmaster/pgarch.c +++ b/src/backend/postmaster/pgarch.c @@ -48,6 +48,7 @@ #include "storage/latch.h" #include "storage/pg_shmem.h" #include "storage/pmsignal.h" +#include "storage/procsignal.h" #include "utils/guc.h" #include "utils/ps_status.h" @@ -78,7 +79,6 @@ * Local data * ---------- */ -static time_t last_pgarch_start_time; static time_t last_sigterm_time = 0; /* @@ -95,8 +95,6 @@ static volatile sig_atomic_t ready_to_stop = false; static pid_t pgarch_forkexec(void); #endif -NON_EXEC_STATIC void PgArchiverMain(int argc, char *argv[]) pg_attribute_noreturn(); -static void pgarch_waken(SIGNAL_ARGS); static void pgarch_waken_stop(SIGNAL_ARGS); static void pgarch_MainLoop(void); static void pgarch_ArchiverCopyLoop(void); @@ -110,75 +108,6 @@ static void pgarch_archiveDone(char *xlog); * ------------------------------------------------------------ */ -/* - * pgarch_start - * - * Called from postmaster at startup or after an existing archiver - * died. Attempt to fire up a fresh archiver process. - * - * Returns PID of child process, or 0 if fail. - * - * Note: if fail, we will be called again from the postmaster main loop. - */ -int -pgarch_start(void) -{ - time_t curtime; - pid_t pgArchPid; - - /* - * Do nothing if no archiver needed - */ - if (!XLogArchivingActive()) - return 0; - - /* - * Do nothing if too soon since last archiver start. This is a safety - * valve to protect against continuous respawn attempts if the archiver is - * dying immediately at launch. Note that since we will be re-called from - * the postmaster main loop, we will get another chance later. - */ - curtime = time(NULL); - if ((unsigned int) (curtime - last_pgarch_start_time) < - (unsigned int) PGARCH_RESTART_INTERVAL) - return 0; - last_pgarch_start_time = curtime; - -#ifdef EXEC_BACKEND - switch ((pgArchPid = pgarch_forkexec())) -#else - switch ((pgArchPid = fork_process())) -#endif - { - case -1: - ereport(LOG, - (errmsg("could not fork archiver: %m"))); - return 0; - -#ifndef EXEC_BACKEND - case 0: - /* in postmaster child ... */ - InitPostmasterChild(); - - /* Close the postmaster's sockets */ - ClosePostmasterPorts(false); - - /* Drop our connection to postmaster's shared memory, as well */ - dsm_detach_all(); - PGSharedMemoryDetach(); - - PgArchiverMain(0, NULL); - break; -#endif - - default: - return (int) pgArchPid; - } - - /* shouldn't get here */ - return 0; -} - /* ------------------------------------------------------------ * Local functions called by archiver follow * ------------------------------------------------------------ @@ -212,14 +141,21 @@ pgarch_forkexec(void) #endif /* EXEC_BACKEND */ +/* Clean up notification stuff on exit */ +static void +PgArchiverKill(int code, Datum arg) +{ + XLogArchiveWakeupEnd(); +} + /* * PgArchiverMain * * The argc/argv parameters are valid only in EXEC_BACKEND case. However, * since we don't use 'em, it hardly matters... */ -NON_EXEC_STATIC void -PgArchiverMain(int argc, char *argv[]) +void +PgArchiverMain(void) { /* * Ignore all signals usually bound to some action in the postmaster, @@ -231,7 +167,7 @@ PgArchiverMain(int argc, char *argv[]) pqsignal(SIGQUIT, SignalHandlerForCrashExit); pqsignal(SIGALRM, SIG_IGN); pqsignal(SIGPIPE, SIG_IGN); - pqsignal(SIGUSR1, pgarch_waken); + pqsignal(SIGUSR1, procsignal_sigusr1_handler); pqsignal(SIGUSR2, pgarch_waken_stop); /* Reset some signals that are accepted by postmaster but not here */ pqsignal(SIGCHLD, SIG_DFL); @@ -240,24 +176,14 @@ PgArchiverMain(int argc, char *argv[]) MyBackendType = B_ARCHIVER; init_ps_display(NULL); + XLogArchiveWakeupStart(); + on_shmem_exit(PgArchiverKill, 0); + pgarch_MainLoop(); exit(0); } -/* SIGUSR1 signal handler for archiver process */ -static void -pgarch_waken(SIGNAL_ARGS) -{ - int save_errno = errno; - - /* set flag that there is work to be done */ - wakened = true; - SetLatch(MyLatch); - - errno = save_errno; -} - /* SIGUSR2 signal handler for archiver process */ static void pgarch_waken_stop(SIGNAL_ARGS) diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index 73d278f3b2..a4b9d212a2 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -146,7 +146,8 @@ #define BACKEND_TYPE_AUTOVAC 0x0002 /* autovacuum worker process */ #define BACKEND_TYPE_WALSND 0x0004 /* walsender process */ #define BACKEND_TYPE_BGWORKER 0x0008 /* bgworker process */ -#define BACKEND_TYPE_ALL 0x000F /* OR of all the above */ +#define BACKEND_TYPE_ARCHIVER 0x0010 /* archiver process */ +#define BACKEND_TYPE_ALL 0x001F /* OR of all the above */ #define BACKEND_TYPE_WORKER (BACKEND_TYPE_AUTOVAC | BACKEND_TYPE_BGWORKER) @@ -539,6 +540,7 @@ static void ShmemBackendArrayRemove(Backend *bn); #endif /* EXEC_BACKEND */ #define StartupDataBase() StartChildProcess(StartupProcess) +#define StartArchiver() StartChildProcess(ArchiverProcess) #define StartBackgroundWriter() StartChildProcess(BgWriterProcess) #define StartCheckpointer() StartChildProcess(CheckpointerProcess) #define StartWalWriter() StartChildProcess(WalWriterProcess) @@ -1785,7 +1787,7 @@ ServerLoop(void) /* If we have lost the archiver, try to start a new one. */ if (PgArchPID == 0 && PgArchStartupAllowed()) - PgArchPID = pgarch_start(); + PgArchPID = StartArchiver(); /* If we need to signal the autovacuum launcher, do so now */ if (avlauncher_needs_signal) @@ -3055,7 +3057,7 @@ reaper(SIGNAL_ARGS) if (!IsBinaryUpgrade && AutoVacuumingActive() && AutoVacPID == 0) AutoVacPID = StartAutoVacLauncher(); if (PgArchStartupAllowed() && PgArchPID == 0) - PgArchPID = pgarch_start(); + PgArchPID = StartArchiver(); if (PgStatPID == 0) PgStatPID = pgstat_start(); @@ -3190,20 +3192,16 @@ reaper(SIGNAL_ARGS) } /* - * Was it the archiver? If so, just try to start a new one; no need - * to force reset of the rest of the system. (If fail, we'll try - * again in future cycles of the main loop.). Unless we were waiting - * for it to shut down; don't restart it in that case, and - * PostmasterStateMachine() will advance to the next shutdown step. + * Was it the archiver? Normal exit can be ignored; we'll start a new + * one at the next iteration of the postmaster's main loop, if + * necessary. Any other exit condition is treated as a crash. */ if (pid == PgArchPID) { PgArchPID = 0; if (!EXIT_STATUS_0(exitstatus)) - LogChildExit(LOG, _("archiver process"), - pid, exitstatus); - if (PgArchStartupAllowed()) - PgArchPID = pgarch_start(); + HandleChildCrash(pid, exitstatus, + _("archiver process")); continue; } @@ -3451,7 +3449,7 @@ CleanupBackend(int pid, /* * HandleChildCrash -- cleanup after failed backend, bgwriter, checkpointer, - * walwriter, autovacuum, or background worker. + * walwriter, autovacuum, archiver or background worker. * * The objectives here are to clean up our local state about the child * process, and to signal all other remaining children to quickdie. @@ -3656,6 +3654,18 @@ HandleChildCrash(int pid, int exitstatus, const char *procname) signal_child(AutoVacPID, (SendStop ? SIGSTOP : SIGQUIT)); } + /* Take care of the archiver too */ + if (pid == PgArchPID) + PgArchPID = 0; + else if (PgArchPID != 0 && take_action) + { + ereport(DEBUG2, + (errmsg_internal("sending %s to process %d", + (SendStop ? "SIGSTOP" : "SIGQUIT"), + (int) PgArchPID))); + signal_child(PgArchPID, (SendStop ? SIGSTOP : SIGQUIT)); + } + /* * Force a power-cycle of the pgarch process too. (This isn't absolutely * necessary, but it seems like a good idea for robustness, and it @@ -3928,6 +3938,7 @@ PostmasterStateMachine(void) Assert(CheckpointerPID == 0); Assert(WalWriterPID == 0); Assert(AutoVacPID == 0); + Assert(PgArchPID == 0); /* syslogger is not considered here */ pmState = PM_NO_CHILDREN; } @@ -5208,7 +5219,7 @@ sigusr1_handler(SIGNAL_ARGS) */ Assert(PgArchPID == 0); if (XLogArchivingAlways()) - PgArchPID = pgarch_start(); + PgArchPID = StartArchiver(); /* * If we aren't planning to enter hot standby mode later, treat @@ -5251,16 +5262,6 @@ sigusr1_handler(SIGNAL_ARGS) if (StartWorkerNeeded || HaveCrashedWorker) maybe_start_bgworkers(); - if (CheckPostmasterSignal(PMSIGNAL_WAKEN_ARCHIVER) && - PgArchPID != 0) - { - /* - * Send SIGUSR1 to archiver process, to wake it up and begin archiving - * next WAL file. - */ - signal_child(PgArchPID, SIGUSR1); - } - /* Tell syslogger to rotate logfile if requested */ if (SysLoggerPID != 0) { @@ -5493,6 +5494,10 @@ StartChildProcess(AuxProcType type) ereport(LOG, (errmsg("could not fork startup process: %m"))); break; + case ArchiverProcess: + ereport(LOG, + (errmsg("could not fork archiver process: %m"))); + break; case BgWriterProcess: ereport(LOG, (errmsg("could not fork background writer process: %m"))); diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h index 2b1b67d35c..cc134fd61e 100644 --- a/src/include/access/xlog.h +++ b/src/include/access/xlog.h @@ -312,6 +312,8 @@ extern XLogRecPtr GetRedoRecPtr(void); extern XLogRecPtr GetInsertRecPtr(void); extern XLogRecPtr GetFlushRecPtr(void); extern XLogRecPtr GetLastImportantRecPtr(void); +extern void XLogArchiveWakeupStart(void); +extern void XLogArchiveWakeupEnd(void); extern void RemovePromoteSignalFiles(void); extern bool PromoteIsTriggered(void); diff --git a/src/include/access/xlog_internal.h b/src/include/access/xlog_internal.h index 27ded593ab..a272d62b1f 100644 --- a/src/include/access/xlog_internal.h +++ b/src/include/access/xlog_internal.h @@ -331,6 +331,7 @@ extern void ExecuteRecoveryCommand(const char *command, const char *commandName, extern void KeepFileRestoredFromArchive(const char *path, const char *xlogfname); extern void XLogArchiveNotify(const char *xlog); extern void XLogArchiveNotifySeg(XLogSegNo segno); +extern void XLogArchiveWakeup(void); extern void XLogArchiveForceDone(const char *xlog); extern bool XLogArchiveCheckDone(const char *xlog); extern bool XLogArchiveIsBusy(const char *xlog); diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index 14fa127ab1..619b2f9c71 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -417,6 +417,7 @@ typedef enum BootstrapProcess, StartupProcess, BgWriterProcess, + ArchiverProcess, CheckpointerProcess, WalWriterProcess, WalReceiverProcess, @@ -429,6 +430,7 @@ extern AuxProcType MyAuxProcType; #define AmBootstrapProcess() (MyAuxProcType == BootstrapProcess) #define AmStartupProcess() (MyAuxProcType == StartupProcess) #define AmBackgroundWriterProcess() (MyAuxProcType == BgWriterProcess) +#define AmArchiverProcess() (MyAuxProcType == ArchiverProcess) #define AmCheckpointerProcess() (MyAuxProcType == CheckpointerProcess) #define AmWalWriterProcess() (MyAuxProcType == WalWriterProcess) #define AmWalReceiverProcess() (MyAuxProcType == WalReceiverProcess) diff --git a/src/include/postmaster/pgarch.h b/src/include/postmaster/pgarch.h index b3200874ca..e3ffc63f14 100644 --- a/src/include/postmaster/pgarch.h +++ b/src/include/postmaster/pgarch.h @@ -32,8 +32,6 @@ */ extern int pgarch_start(void); -#ifdef EXEC_BACKEND -extern void PgArchiverMain(int argc, char *argv[]) pg_attribute_noreturn(); -#endif +extern void PgArchiverMain(void) pg_attribute_noreturn(); #endif /* _PGARCH_H */ diff --git a/src/include/storage/pmsignal.h b/src/include/storage/pmsignal.h index 56c5ec4481..c691acf8cd 100644 --- a/src/include/storage/pmsignal.h +++ b/src/include/storage/pmsignal.h @@ -34,7 +34,6 @@ typedef enum { PMSIGNAL_RECOVERY_STARTED, /* recovery has started */ PMSIGNAL_BEGIN_HOT_STANDBY, /* begin Hot Standby */ - PMSIGNAL_WAKEN_ARCHIVER, /* send a NOTIFY signal to xlog archiver */ PMSIGNAL_ROTATE_LOGFILE, /* send SIGUSR1 to syslogger to rotate logfile */ PMSIGNAL_START_AUTOVAC_LAUNCHER, /* start an autovacuum launcher */ PMSIGNAL_START_AUTOVAC_WORKER, /* start an autovacuum worker */ -- 2.18.2 ----Next_Part(Mon_Mar_30_09_29_45_2020_582)-- Content-Type: Text/X-Patch; charset=us-ascii Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="v28-0005-Shared-memory-based-stats-collector.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v27 4/7] Make archiver process an auxiliary process @ 2020-03-13 07:59 Kyotaro Horiguchi <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Kyotaro Horiguchi @ 2020-03-13 07:59 UTC (permalink / raw) This is a preliminary patch for shared-memory based stats collector. Archiver process must be a auxiliary process since it uses shared memory after stats data was moved into shared-memory. Make the process an auxiliary process in order to make it work. --- src/backend/access/transam/xlog.c | 49 +++++++++++ src/backend/access/transam/xlogarchive.c | 2 +- src/backend/bootstrap/bootstrap.c | 22 ++--- src/backend/postmaster/pgarch.c | 102 ++++------------------- src/backend/postmaster/postmaster.c | 53 ++++++------ src/include/access/xlog.h | 2 + src/include/access/xlog_internal.h | 1 + src/include/miscadmin.h | 2 + src/include/postmaster/pgarch.h | 4 +- src/include/storage/pmsignal.h | 1 - 10 files changed, 111 insertions(+), 127 deletions(-) diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index 7621fc05e2..4da7ed3657 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -680,6 +680,13 @@ typedef struct XLogCtlData */ Latch recoveryWakeupLatch; + /* + * archiverWakeupLatch is used to wake up the archiver process to process + * completed WAL segments, if it is waiting for WAL to arrive. + * Protected by info_lck. + */ + Latch *archiverWakeupLatch; + /* * During recovery, we keep a copy of the latest checkpoint record here. * lastCheckPointRecPtr points to start of checkpoint record and @@ -8381,6 +8388,48 @@ GetLastSegSwitchData(XLogRecPtr *lastSwitchLSN) return result; } +/* + * XLogArchiveWakeupEnd - Set up archiver wakeup stuff + */ +void +XLogArchiveWakeupStart(void) +{ + Latch *old_latch PG_USED_FOR_ASSERTS_ONLY; + + SpinLockAcquire(&XLogCtl->info_lck); + old_latch = XLogCtl->archiverWakeupLatch; + XLogCtl->archiverWakeupLatch = MyLatch; + SpinLockRelease(&XLogCtl->info_lck); + Assert (old_latch == NULL); +} + +/* + * XLogArchiveWakeupEnd - Clean up archiver wakeup stuff + */ +void +XLogArchiveWakeupEnd(void) +{ + SpinLockAcquire(&XLogCtl->info_lck); + XLogCtl->archiverWakeupLatch = NULL; + SpinLockRelease(&XLogCtl->info_lck); +} + +/* + * XLogWakeupArchiver - Wake up archiver process + */ +void +XLogArchiveWakeup(void) +{ + Latch *latch; + + SpinLockAcquire(&XLogCtl->info_lck); + latch = XLogCtl->archiverWakeupLatch; + SpinLockRelease(&XLogCtl->info_lck); + + if (latch) + SetLatch(latch); +} + /* * This must be called ONCE during postmaster or standalone-backend shutdown */ diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c index 914ad340ea..47c2b4a373 100644 --- a/src/backend/access/transam/xlogarchive.c +++ b/src/backend/access/transam/xlogarchive.c @@ -489,7 +489,7 @@ XLogArchiveNotify(const char *xlog) /* Notify archiver that it's got something to do */ if (IsUnderPostmaster) - SendPostmasterSignal(PMSIGNAL_WAKEN_ARCHIVER); + XLogArchiveWakeup(); } /* diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c index 5480a024e0..d398ce6f03 100644 --- a/src/backend/bootstrap/bootstrap.c +++ b/src/backend/bootstrap/bootstrap.c @@ -319,6 +319,9 @@ AuxiliaryProcessMain(int argc, char *argv[]) case StartupProcess: MyBackendType = B_STARTUP; break; + case ArchiverProcess: + MyBackendType = B_ARCHIVER; + break; case BgWriterProcess: MyBackendType = B_BG_WRITER; break; @@ -439,30 +442,29 @@ AuxiliaryProcessMain(int argc, char *argv[]) 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 */ + proc_exit(1); + + case ArchiverProcess: + PgArchiverMain(); + proc_exit(1); case BgWriterProcess: - /* don't set signals, bgwriter has its own agenda */ BackgroundWriterMain(); - proc_exit(1); /* should never return */ + proc_exit(1); case CheckpointerProcess: - /* don't set signals, checkpointer has its own agenda */ CheckpointerMain(); - proc_exit(1); /* should never return */ + proc_exit(1); case WalWriterProcess: - /* don't set signals, walwriter has its own agenda */ InitXLOGAccess(); WalWriterMain(); - proc_exit(1); /* should never return */ + proc_exit(1); case WalReceiverProcess: - /* don't set signals, walreceiver has its own agenda */ WalReceiverMain(); - proc_exit(1); /* should never return */ + proc_exit(1); default: elog(PANIC, "unrecognized process type: %d", (int) MyAuxProcType); diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c index 37be0e2bbb..6fe7a136ba 100644 --- a/src/backend/postmaster/pgarch.c +++ b/src/backend/postmaster/pgarch.c @@ -48,6 +48,7 @@ #include "storage/latch.h" #include "storage/pg_shmem.h" #include "storage/pmsignal.h" +#include "storage/procsignal.h" #include "utils/guc.h" #include "utils/ps_status.h" @@ -78,7 +79,6 @@ * Local data * ---------- */ -static time_t last_pgarch_start_time; static time_t last_sigterm_time = 0; /* @@ -95,8 +95,6 @@ static volatile sig_atomic_t ready_to_stop = false; static pid_t pgarch_forkexec(void); #endif -NON_EXEC_STATIC void PgArchiverMain(int argc, char *argv[]) pg_attribute_noreturn(); -static void pgarch_waken(SIGNAL_ARGS); static void pgarch_waken_stop(SIGNAL_ARGS); static void pgarch_MainLoop(void); static void pgarch_ArchiverCopyLoop(void); @@ -110,75 +108,6 @@ static void pgarch_archiveDone(char *xlog); * ------------------------------------------------------------ */ -/* - * pgarch_start - * - * Called from postmaster at startup or after an existing archiver - * died. Attempt to fire up a fresh archiver process. - * - * Returns PID of child process, or 0 if fail. - * - * Note: if fail, we will be called again from the postmaster main loop. - */ -int -pgarch_start(void) -{ - time_t curtime; - pid_t pgArchPid; - - /* - * Do nothing if no archiver needed - */ - if (!XLogArchivingActive()) - return 0; - - /* - * Do nothing if too soon since last archiver start. This is a safety - * valve to protect against continuous respawn attempts if the archiver is - * dying immediately at launch. Note that since we will be re-called from - * the postmaster main loop, we will get another chance later. - */ - curtime = time(NULL); - if ((unsigned int) (curtime - last_pgarch_start_time) < - (unsigned int) PGARCH_RESTART_INTERVAL) - return 0; - last_pgarch_start_time = curtime; - -#ifdef EXEC_BACKEND - switch ((pgArchPid = pgarch_forkexec())) -#else - switch ((pgArchPid = fork_process())) -#endif - { - case -1: - ereport(LOG, - (errmsg("could not fork archiver: %m"))); - return 0; - -#ifndef EXEC_BACKEND - case 0: - /* in postmaster child ... */ - InitPostmasterChild(); - - /* Close the postmaster's sockets */ - ClosePostmasterPorts(false); - - /* Drop our connection to postmaster's shared memory, as well */ - dsm_detach_all(); - PGSharedMemoryDetach(); - - PgArchiverMain(0, NULL); - break; -#endif - - default: - return (int) pgArchPid; - } - - /* shouldn't get here */ - return 0; -} - /* ------------------------------------------------------------ * Local functions called by archiver follow * ------------------------------------------------------------ @@ -212,14 +141,21 @@ pgarch_forkexec(void) #endif /* EXEC_BACKEND */ +/* Clean up notification stuff on exit */ +static void +PgArchiverKill(int code, Datum arg) +{ + XLogArchiveWakeupEnd(); +} + /* * PgArchiverMain * * The argc/argv parameters are valid only in EXEC_BACKEND case. However, * since we don't use 'em, it hardly matters... */ -NON_EXEC_STATIC void -PgArchiverMain(int argc, char *argv[]) +void +PgArchiverMain(void) { /* * Ignore all signals usually bound to some action in the postmaster, @@ -231,7 +167,7 @@ PgArchiverMain(int argc, char *argv[]) pqsignal(SIGQUIT, SignalHandlerForCrashExit); pqsignal(SIGALRM, SIG_IGN); pqsignal(SIGPIPE, SIG_IGN); - pqsignal(SIGUSR1, pgarch_waken); + pqsignal(SIGUSR1, procsignal_sigusr1_handler); pqsignal(SIGUSR2, pgarch_waken_stop); /* Reset some signals that are accepted by postmaster but not here */ pqsignal(SIGCHLD, SIG_DFL); @@ -240,24 +176,14 @@ PgArchiverMain(int argc, char *argv[]) MyBackendType = B_ARCHIVER; init_ps_display(NULL); + XLogArchiveWakeupStart(); + on_shmem_exit(PgArchiverKill, 0); + pgarch_MainLoop(); exit(0); } -/* SIGUSR1 signal handler for archiver process */ -static void -pgarch_waken(SIGNAL_ARGS) -{ - int save_errno = errno; - - /* set flag that there is work to be done */ - wakened = true; - SetLatch(MyLatch); - - errno = save_errno; -} - /* SIGUSR2 signal handler for archiver process */ static void pgarch_waken_stop(SIGNAL_ARGS) diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index 2b9ab32293..fab4a9dd51 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -146,7 +146,8 @@ #define BACKEND_TYPE_AUTOVAC 0x0002 /* autovacuum worker process */ #define BACKEND_TYPE_WALSND 0x0004 /* walsender process */ #define BACKEND_TYPE_BGWORKER 0x0008 /* bgworker process */ -#define BACKEND_TYPE_ALL 0x000F /* OR of all the above */ +#define BACKEND_TYPE_ARCHIVER 0x0010 /* archiver process */ +#define BACKEND_TYPE_ALL 0x001F /* OR of all the above */ #define BACKEND_TYPE_WORKER (BACKEND_TYPE_AUTOVAC | BACKEND_TYPE_BGWORKER) @@ -539,6 +540,7 @@ static void ShmemBackendArrayRemove(Backend *bn); #endif /* EXEC_BACKEND */ #define StartupDataBase() StartChildProcess(StartupProcess) +#define StartArchiver() StartChildProcess(ArchiverProcess) #define StartBackgroundWriter() StartChildProcess(BgWriterProcess) #define StartCheckpointer() StartChildProcess(CheckpointerProcess) #define StartWalWriter() StartChildProcess(WalWriterProcess) @@ -1785,7 +1787,7 @@ ServerLoop(void) /* If we have lost the archiver, try to start a new one. */ if (PgArchPID == 0 && PgArchStartupAllowed()) - PgArchPID = pgarch_start(); + PgArchPID = StartArchiver(); /* If we need to signal the autovacuum launcher, do so now */ if (avlauncher_needs_signal) @@ -3055,7 +3057,7 @@ reaper(SIGNAL_ARGS) if (!IsBinaryUpgrade && AutoVacuumingActive() && AutoVacPID == 0) AutoVacPID = StartAutoVacLauncher(); if (PgArchStartupAllowed() && PgArchPID == 0) - PgArchPID = pgarch_start(); + PgArchPID = StartArchiver(); if (PgStatPID == 0) PgStatPID = pgstat_start(); @@ -3190,20 +3192,16 @@ reaper(SIGNAL_ARGS) } /* - * Was it the archiver? If so, just try to start a new one; no need - * to force reset of the rest of the system. (If fail, we'll try - * again in future cycles of the main loop.). Unless we were waiting - * for it to shut down; don't restart it in that case, and - * PostmasterStateMachine() will advance to the next shutdown step. + * Was it the archiver? Normal exit can be ignored; we'll start a new + * one at the next iteration of the postmaster's main loop, if + * necessary. Any other exit condition is treated as a crash. */ if (pid == PgArchPID) { PgArchPID = 0; if (!EXIT_STATUS_0(exitstatus)) - LogChildExit(LOG, _("archiver process"), - pid, exitstatus); - if (PgArchStartupAllowed()) - PgArchPID = pgarch_start(); + HandleChildCrash(pid, exitstatus, + _("archiver process")); continue; } @@ -3451,7 +3449,7 @@ CleanupBackend(int pid, /* * HandleChildCrash -- cleanup after failed backend, bgwriter, checkpointer, - * walwriter, autovacuum, or background worker. + * walwriter, autovacuum, archiver or background worker. * * The objectives here are to clean up our local state about the child * process, and to signal all other remaining children to quickdie. @@ -3656,6 +3654,18 @@ HandleChildCrash(int pid, int exitstatus, const char *procname) signal_child(AutoVacPID, (SendStop ? SIGSTOP : SIGQUIT)); } + /* Take care of the archiver too */ + if (pid == PgArchPID) + PgArchPID = 0; + else if (PgArchPID != 0 && take_action) + { + ereport(DEBUG2, + (errmsg_internal("sending %s to process %d", + (SendStop ? "SIGSTOP" : "SIGQUIT"), + (int) PgArchPID))); + signal_child(PgArchPID, (SendStop ? SIGSTOP : SIGQUIT)); + } + /* * Force a power-cycle of the pgarch process too. (This isn't absolutely * necessary, but it seems like a good idea for robustness, and it @@ -3928,6 +3938,7 @@ PostmasterStateMachine(void) Assert(CheckpointerPID == 0); Assert(WalWriterPID == 0); Assert(AutoVacPID == 0); + Assert(PgArchPID == 0); /* syslogger is not considered here */ pmState = PM_NO_CHILDREN; } @@ -5208,7 +5219,7 @@ sigusr1_handler(SIGNAL_ARGS) */ Assert(PgArchPID == 0); if (XLogArchivingAlways()) - PgArchPID = pgarch_start(); + PgArchPID = StartArchiver(); /* * If we aren't planning to enter hot standby mode later, treat @@ -5251,16 +5262,6 @@ sigusr1_handler(SIGNAL_ARGS) if (StartWorkerNeeded || HaveCrashedWorker) maybe_start_bgworkers(); - if (CheckPostmasterSignal(PMSIGNAL_WAKEN_ARCHIVER) && - PgArchPID != 0) - { - /* - * Send SIGUSR1 to archiver process, to wake it up and begin archiving - * next WAL file. - */ - signal_child(PgArchPID, SIGUSR1); - } - /* Tell syslogger to rotate logfile if requested */ if (SysLoggerPID != 0) { @@ -5493,6 +5494,10 @@ StartChildProcess(AuxProcType type) ereport(LOG, (errmsg("could not fork startup process: %m"))); break; + case ArchiverProcess: + ereport(LOG, + (errmsg("could not fork archiver process: %m"))); + break; case BgWriterProcess: ereport(LOG, (errmsg("could not fork background writer process: %m"))); diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h index 331497bcfb..f38eaee092 100644 --- a/src/include/access/xlog.h +++ b/src/include/access/xlog.h @@ -311,6 +311,8 @@ extern XLogRecPtr GetRedoRecPtr(void); extern XLogRecPtr GetInsertRecPtr(void); extern XLogRecPtr GetFlushRecPtr(void); extern XLogRecPtr GetLastImportantRecPtr(void); +extern void XLogArchiveWakeupStart(void); +extern void XLogArchiveWakeupEnd(void); extern void RemovePromoteSignalFiles(void); extern bool PromoteIsTriggered(void); diff --git a/src/include/access/xlog_internal.h b/src/include/access/xlog_internal.h index 27ded593ab..a272d62b1f 100644 --- a/src/include/access/xlog_internal.h +++ b/src/include/access/xlog_internal.h @@ -331,6 +331,7 @@ extern void ExecuteRecoveryCommand(const char *command, const char *commandName, extern void KeepFileRestoredFromArchive(const char *path, const char *xlogfname); extern void XLogArchiveNotify(const char *xlog); extern void XLogArchiveNotifySeg(XLogSegNo segno); +extern void XLogArchiveWakeup(void); extern void XLogArchiveForceDone(const char *xlog); extern bool XLogArchiveCheckDone(const char *xlog); extern bool XLogArchiveIsBusy(const char *xlog); diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index 14fa127ab1..619b2f9c71 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -417,6 +417,7 @@ typedef enum BootstrapProcess, StartupProcess, BgWriterProcess, + ArchiverProcess, CheckpointerProcess, WalWriterProcess, WalReceiverProcess, @@ -429,6 +430,7 @@ extern AuxProcType MyAuxProcType; #define AmBootstrapProcess() (MyAuxProcType == BootstrapProcess) #define AmStartupProcess() (MyAuxProcType == StartupProcess) #define AmBackgroundWriterProcess() (MyAuxProcType == BgWriterProcess) +#define AmArchiverProcess() (MyAuxProcType == ArchiverProcess) #define AmCheckpointerProcess() (MyAuxProcType == CheckpointerProcess) #define AmWalWriterProcess() (MyAuxProcType == WalWriterProcess) #define AmWalReceiverProcess() (MyAuxProcType == WalReceiverProcess) diff --git a/src/include/postmaster/pgarch.h b/src/include/postmaster/pgarch.h index b3200874ca..e3ffc63f14 100644 --- a/src/include/postmaster/pgarch.h +++ b/src/include/postmaster/pgarch.h @@ -32,8 +32,6 @@ */ extern int pgarch_start(void); -#ifdef EXEC_BACKEND -extern void PgArchiverMain(int argc, char *argv[]) pg_attribute_noreturn(); -#endif +extern void PgArchiverMain(void) pg_attribute_noreturn(); #endif /* _PGARCH_H */ diff --git a/src/include/storage/pmsignal.h b/src/include/storage/pmsignal.h index 56c5ec4481..c691acf8cd 100644 --- a/src/include/storage/pmsignal.h +++ b/src/include/storage/pmsignal.h @@ -34,7 +34,6 @@ typedef enum { PMSIGNAL_RECOVERY_STARTED, /* recovery has started */ PMSIGNAL_BEGIN_HOT_STANDBY, /* begin Hot Standby */ - PMSIGNAL_WAKEN_ARCHIVER, /* send a NOTIFY signal to xlog archiver */ PMSIGNAL_ROTATE_LOGFILE, /* send SIGUSR1 to syslogger to rotate logfile */ PMSIGNAL_START_AUTOVAC_LAUNCHER, /* start an autovacuum launcher */ PMSIGNAL_START_AUTOVAC_WORKER, /* start an autovacuum worker */ -- 2.18.2 ----Next_Part(Fri_Mar_27_16_31_15_2020_716)-- Content-Type: Text/X-Patch; charset=us-ascii Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="v27-0005-Shared-memory-based-stats-collector.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v25 4/8] Make archiver process an auxiliary process @ 2020-03-13 07:59 Kyotaro Horiguchi <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Kyotaro Horiguchi @ 2020-03-13 07:59 UTC (permalink / raw) This is a preliminary patch for shared-memory based stats collector. Archiver process must be a auxiliary process since it uses shared memory after stats data was moved into shared-memory. Make the process an auxiliary process in order to make it work. --- src/backend/bootstrap/bootstrap.c | 22 +++++---- src/backend/postmaster/pgarch.c | 75 +---------------------------- src/backend/postmaster/postmaster.c | 43 +++++++++++------ src/include/miscadmin.h | 2 + src/include/postmaster/pgarch.h | 4 +- 5 files changed, 46 insertions(+), 100 deletions(-) diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c index 5480a024e0..d398ce6f03 100644 --- a/src/backend/bootstrap/bootstrap.c +++ b/src/backend/bootstrap/bootstrap.c @@ -319,6 +319,9 @@ AuxiliaryProcessMain(int argc, char *argv[]) case StartupProcess: MyBackendType = B_STARTUP; break; + case ArchiverProcess: + MyBackendType = B_ARCHIVER; + break; case BgWriterProcess: MyBackendType = B_BG_WRITER; break; @@ -439,30 +442,29 @@ AuxiliaryProcessMain(int argc, char *argv[]) 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 */ + proc_exit(1); + + case ArchiverProcess: + PgArchiverMain(); + proc_exit(1); case BgWriterProcess: - /* don't set signals, bgwriter has its own agenda */ BackgroundWriterMain(); - proc_exit(1); /* should never return */ + proc_exit(1); case CheckpointerProcess: - /* don't set signals, checkpointer has its own agenda */ CheckpointerMain(); - proc_exit(1); /* should never return */ + proc_exit(1); case WalWriterProcess: - /* don't set signals, walwriter has its own agenda */ InitXLOGAccess(); WalWriterMain(); - proc_exit(1); /* should never return */ + proc_exit(1); case WalReceiverProcess: - /* don't set signals, walreceiver has its own agenda */ WalReceiverMain(); - proc_exit(1); /* should never return */ + proc_exit(1); default: elog(PANIC, "unrecognized process type: %d", (int) MyAuxProcType); diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c index 37be0e2bbb..4971b3ae42 100644 --- a/src/backend/postmaster/pgarch.c +++ b/src/backend/postmaster/pgarch.c @@ -78,7 +78,6 @@ * Local data * ---------- */ -static time_t last_pgarch_start_time; static time_t last_sigterm_time = 0; /* @@ -95,7 +94,6 @@ static volatile sig_atomic_t ready_to_stop = false; static pid_t pgarch_forkexec(void); #endif -NON_EXEC_STATIC void PgArchiverMain(int argc, char *argv[]) pg_attribute_noreturn(); static void pgarch_waken(SIGNAL_ARGS); static void pgarch_waken_stop(SIGNAL_ARGS); static void pgarch_MainLoop(void); @@ -110,75 +108,6 @@ static void pgarch_archiveDone(char *xlog); * ------------------------------------------------------------ */ -/* - * pgarch_start - * - * Called from postmaster at startup or after an existing archiver - * died. Attempt to fire up a fresh archiver process. - * - * Returns PID of child process, or 0 if fail. - * - * Note: if fail, we will be called again from the postmaster main loop. - */ -int -pgarch_start(void) -{ - time_t curtime; - pid_t pgArchPid; - - /* - * Do nothing if no archiver needed - */ - if (!XLogArchivingActive()) - return 0; - - /* - * Do nothing if too soon since last archiver start. This is a safety - * valve to protect against continuous respawn attempts if the archiver is - * dying immediately at launch. Note that since we will be re-called from - * the postmaster main loop, we will get another chance later. - */ - curtime = time(NULL); - if ((unsigned int) (curtime - last_pgarch_start_time) < - (unsigned int) PGARCH_RESTART_INTERVAL) - return 0; - last_pgarch_start_time = curtime; - -#ifdef EXEC_BACKEND - switch ((pgArchPid = pgarch_forkexec())) -#else - switch ((pgArchPid = fork_process())) -#endif - { - case -1: - ereport(LOG, - (errmsg("could not fork archiver: %m"))); - return 0; - -#ifndef EXEC_BACKEND - case 0: - /* in postmaster child ... */ - InitPostmasterChild(); - - /* Close the postmaster's sockets */ - ClosePostmasterPorts(false); - - /* Drop our connection to postmaster's shared memory, as well */ - dsm_detach_all(); - PGSharedMemoryDetach(); - - PgArchiverMain(0, NULL); - break; -#endif - - default: - return (int) pgArchPid; - } - - /* shouldn't get here */ - return 0; -} - /* ------------------------------------------------------------ * Local functions called by archiver follow * ------------------------------------------------------------ @@ -218,8 +147,8 @@ pgarch_forkexec(void) * The argc/argv parameters are valid only in EXEC_BACKEND case. However, * since we don't use 'em, it hardly matters... */ -NON_EXEC_STATIC void -PgArchiverMain(int argc, char *argv[]) +void +PgArchiverMain(void) { /* * Ignore all signals usually bound to some action in the postmaster, diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index 2b9ab32293..cab7fb5381 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -146,7 +146,8 @@ #define BACKEND_TYPE_AUTOVAC 0x0002 /* autovacuum worker process */ #define BACKEND_TYPE_WALSND 0x0004 /* walsender process */ #define BACKEND_TYPE_BGWORKER 0x0008 /* bgworker process */ -#define BACKEND_TYPE_ALL 0x000F /* OR of all the above */ +#define BACKEND_TYPE_ARCHIVER 0x0010 /* archiver process */ +#define BACKEND_TYPE_ALL 0x001F /* OR of all the above */ #define BACKEND_TYPE_WORKER (BACKEND_TYPE_AUTOVAC | BACKEND_TYPE_BGWORKER) @@ -539,6 +540,7 @@ static void ShmemBackendArrayRemove(Backend *bn); #endif /* EXEC_BACKEND */ #define StartupDataBase() StartChildProcess(StartupProcess) +#define StartArchiver() StartChildProcess(ArchiverProcess) #define StartBackgroundWriter() StartChildProcess(BgWriterProcess) #define StartCheckpointer() StartChildProcess(CheckpointerProcess) #define StartWalWriter() StartChildProcess(WalWriterProcess) @@ -1785,7 +1787,7 @@ ServerLoop(void) /* If we have lost the archiver, try to start a new one. */ if (PgArchPID == 0 && PgArchStartupAllowed()) - PgArchPID = pgarch_start(); + PgArchPID = StartArchiver(); /* If we need to signal the autovacuum launcher, do so now */ if (avlauncher_needs_signal) @@ -3055,7 +3057,7 @@ reaper(SIGNAL_ARGS) if (!IsBinaryUpgrade && AutoVacuumingActive() && AutoVacPID == 0) AutoVacPID = StartAutoVacLauncher(); if (PgArchStartupAllowed() && PgArchPID == 0) - PgArchPID = pgarch_start(); + PgArchPID = StartArchiver(); if (PgStatPID == 0) PgStatPID = pgstat_start(); @@ -3190,20 +3192,16 @@ reaper(SIGNAL_ARGS) } /* - * Was it the archiver? If so, just try to start a new one; no need - * to force reset of the rest of the system. (If fail, we'll try - * again in future cycles of the main loop.). Unless we were waiting - * for it to shut down; don't restart it in that case, and - * PostmasterStateMachine() will advance to the next shutdown step. + * Was it the archiver? Normal exit can be ignored; we'll start a new + * one at the next iteration of the postmaster's main loop, if + * necessary. Any other exit condition is treated as a crash. */ if (pid == PgArchPID) { PgArchPID = 0; if (!EXIT_STATUS_0(exitstatus)) - LogChildExit(LOG, _("archiver process"), - pid, exitstatus); - if (PgArchStartupAllowed()) - PgArchPID = pgarch_start(); + HandleChildCrash(pid, exitstatus, + _("archiver process")); continue; } @@ -3451,7 +3449,7 @@ CleanupBackend(int pid, /* * HandleChildCrash -- cleanup after failed backend, bgwriter, checkpointer, - * walwriter, autovacuum, or background worker. + * walwriter, autovacuum, archiver or background worker. * * The objectives here are to clean up our local state about the child * process, and to signal all other remaining children to quickdie. @@ -3656,6 +3654,18 @@ HandleChildCrash(int pid, int exitstatus, const char *procname) signal_child(AutoVacPID, (SendStop ? SIGSTOP : SIGQUIT)); } + /* Take care of the archiver too */ + if (pid == PgArchPID) + PgArchPID = 0; + else if (PgArchPID != 0 && take_action) + { + ereport(DEBUG2, + (errmsg_internal("sending %s to process %d", + (SendStop ? "SIGSTOP" : "SIGQUIT"), + (int) PgArchPID))); + signal_child(PgArchPID, (SendStop ? SIGSTOP : SIGQUIT)); + } + /* * Force a power-cycle of the pgarch process too. (This isn't absolutely * necessary, but it seems like a good idea for robustness, and it @@ -3928,6 +3938,7 @@ PostmasterStateMachine(void) Assert(CheckpointerPID == 0); Assert(WalWriterPID == 0); Assert(AutoVacPID == 0); + Assert(PgArchPID == 0); /* syslogger is not considered here */ pmState = PM_NO_CHILDREN; } @@ -5208,7 +5219,7 @@ sigusr1_handler(SIGNAL_ARGS) */ Assert(PgArchPID == 0); if (XLogArchivingAlways()) - PgArchPID = pgarch_start(); + PgArchPID = StartArchiver(); /* * If we aren't planning to enter hot standby mode later, treat @@ -5493,6 +5504,10 @@ StartChildProcess(AuxProcType type) ereport(LOG, (errmsg("could not fork startup process: %m"))); break; + case ArchiverProcess: + ereport(LOG, + (errmsg("could not fork archiver process: %m"))); + break; case BgWriterProcess: ereport(LOG, (errmsg("could not fork background writer process: %m"))); diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index 14fa127ab1..619b2f9c71 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -417,6 +417,7 @@ typedef enum BootstrapProcess, StartupProcess, BgWriterProcess, + ArchiverProcess, CheckpointerProcess, WalWriterProcess, WalReceiverProcess, @@ -429,6 +430,7 @@ extern AuxProcType MyAuxProcType; #define AmBootstrapProcess() (MyAuxProcType == BootstrapProcess) #define AmStartupProcess() (MyAuxProcType == StartupProcess) #define AmBackgroundWriterProcess() (MyAuxProcType == BgWriterProcess) +#define AmArchiverProcess() (MyAuxProcType == ArchiverProcess) #define AmCheckpointerProcess() (MyAuxProcType == CheckpointerProcess) #define AmWalWriterProcess() (MyAuxProcType == WalWriterProcess) #define AmWalReceiverProcess() (MyAuxProcType == WalReceiverProcess) diff --git a/src/include/postmaster/pgarch.h b/src/include/postmaster/pgarch.h index b3200874ca..e3ffc63f14 100644 --- a/src/include/postmaster/pgarch.h +++ b/src/include/postmaster/pgarch.h @@ -32,8 +32,6 @@ */ extern int pgarch_start(void); -#ifdef EXEC_BACKEND -extern void PgArchiverMain(int argc, char *argv[]) pg_attribute_noreturn(); -#endif +extern void PgArchiverMain(void) pg_attribute_noreturn(); #endif /* _PGARCH_H */ -- 2.18.2 ----Next_Part(Thu_Mar_19_20_30_04_2020_284)-- Content-Type: Text/X-Patch; charset=us-ascii Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="v25-0005-Use-latch-instead-of-SIGUSR1-to-wake-up-archiver.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH 1/1] allow binaryheap to use any type @ 2023-09-02 18:48 Nathan Bossart <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nathan Bossart @ 2023-09-02 18:48 UTC (permalink / raw) --- src/backend/executor/nodeGatherMerge.c | 20 +++--- src/backend/executor/nodeMergeAppend.c | 20 +++--- src/backend/lib/binaryheap.c | 71 ++++++++++--------- src/backend/postmaster/pgarch.c | 37 +++++----- .../replication/logical/reorderbuffer.c | 22 +++--- src/backend/storage/buffer/bufmgr.c | 21 +++--- src/include/lib/binaryheap.h | 17 ++--- 7 files changed, 112 insertions(+), 96 deletions(-) diff --git a/src/backend/executor/nodeGatherMerge.c b/src/backend/executor/nodeGatherMerge.c index 9d5e1a46e9..4c75d224c8 100644 --- a/src/backend/executor/nodeGatherMerge.c +++ b/src/backend/executor/nodeGatherMerge.c @@ -52,7 +52,7 @@ typedef struct GMReaderTupleBuffer } GMReaderTupleBuffer; static TupleTableSlot *ExecGatherMerge(PlanState *pstate); -static int32 heap_compare_slots(Datum a, Datum b, void *arg); +static int32 heap_compare_slots(void *a, void *b, void *arg); static TupleTableSlot *gather_merge_getnext(GatherMergeState *gm_state); static MinimalTuple gm_readnext_tuple(GatherMergeState *gm_state, int nreader, bool nowait, bool *done); @@ -428,7 +428,7 @@ gather_merge_setup(GatherMergeState *gm_state) } /* Allocate the resources for the merge */ - gm_state->gm_heap = binaryheap_allocate(nreaders + 1, + gm_state->gm_heap = binaryheap_allocate(nreaders + 1, sizeof(int), heap_compare_slots, gm_state); } @@ -489,7 +489,7 @@ reread: /* Don't have a tuple yet, try to get one */ if (gather_merge_readnext(gm_state, i, nowait)) binaryheap_add_unordered(gm_state->gm_heap, - Int32GetDatum(i)); + &i); } else { @@ -565,14 +565,14 @@ gather_merge_getnext(GatherMergeState *gm_state) * the heap, because it might now compare differently against the * other elements of the heap. */ - i = DatumGetInt32(binaryheap_first(gm_state->gm_heap)); + binaryheap_first(gm_state->gm_heap, &i); if (gather_merge_readnext(gm_state, i, false)) - binaryheap_replace_first(gm_state->gm_heap, Int32GetDatum(i)); + binaryheap_replace_first(gm_state->gm_heap, &i); else { /* reader exhausted, remove it from heap */ - (void) binaryheap_remove_first(gm_state->gm_heap); + binaryheap_remove_first(gm_state->gm_heap, &i); } } @@ -585,7 +585,7 @@ gather_merge_getnext(GatherMergeState *gm_state) else { /* Return next tuple from whichever participant has the leading one */ - i = DatumGetInt32(binaryheap_first(gm_state->gm_heap)); + binaryheap_first(gm_state->gm_heap, &i); return gm_state->gm_slots[i]; } } @@ -750,11 +750,11 @@ typedef int32 SlotNumber; * Compare the tuples in the two given slots. */ static int32 -heap_compare_slots(Datum a, Datum b, void *arg) +heap_compare_slots(void *a, void *b, void *arg) { GatherMergeState *node = (GatherMergeState *) arg; - SlotNumber slot1 = DatumGetInt32(a); - SlotNumber slot2 = DatumGetInt32(b); + SlotNumber slot1 = *((int *) a); + SlotNumber slot2 = *((int *) b); TupleTableSlot *s1 = node->gm_slots[slot1]; TupleTableSlot *s2 = node->gm_slots[slot2]; diff --git a/src/backend/executor/nodeMergeAppend.c b/src/backend/executor/nodeMergeAppend.c index 21b5726e6e..928b4b3719 100644 --- a/src/backend/executor/nodeMergeAppend.c +++ b/src/backend/executor/nodeMergeAppend.c @@ -52,7 +52,7 @@ typedef int32 SlotNumber; static TupleTableSlot *ExecMergeAppend(PlanState *pstate); -static int heap_compare_slots(Datum a, Datum b, void *arg); +static int heap_compare_slots(void *a, void *b, void *arg); /* ---------------------------------------------------------------- @@ -125,7 +125,7 @@ ExecInitMergeAppend(MergeAppend *node, EState *estate, int eflags) mergestate->ms_nplans = nplans; mergestate->ms_slots = (TupleTableSlot **) palloc0(sizeof(TupleTableSlot *) * nplans); - mergestate->ms_heap = binaryheap_allocate(nplans, heap_compare_slots, + mergestate->ms_heap = binaryheap_allocate(nplans, sizeof(int), heap_compare_slots, mergestate); /* @@ -229,7 +229,7 @@ ExecMergeAppend(PlanState *pstate) { node->ms_slots[i] = ExecProcNode(node->mergeplans[i]); if (!TupIsNull(node->ms_slots[i])) - binaryheap_add_unordered(node->ms_heap, Int32GetDatum(i)); + binaryheap_add_unordered(node->ms_heap, &i); } binaryheap_build(node->ms_heap); node->ms_initialized = true; @@ -244,12 +244,12 @@ ExecMergeAppend(PlanState *pstate) * by doing this before returning from the prior call, but it's better * to not pull tuples until necessary.) */ - i = DatumGetInt32(binaryheap_first(node->ms_heap)); + binaryheap_first(node->ms_heap, &i); node->ms_slots[i] = ExecProcNode(node->mergeplans[i]); if (!TupIsNull(node->ms_slots[i])) - binaryheap_replace_first(node->ms_heap, Int32GetDatum(i)); + binaryheap_replace_first(node->ms_heap, &i); else - (void) binaryheap_remove_first(node->ms_heap); + binaryheap_remove_first(node->ms_heap, &i); } if (binaryheap_empty(node->ms_heap)) @@ -259,7 +259,7 @@ ExecMergeAppend(PlanState *pstate) } else { - i = DatumGetInt32(binaryheap_first(node->ms_heap)); + binaryheap_first(node->ms_heap, &i); result = node->ms_slots[i]; } @@ -270,11 +270,11 @@ ExecMergeAppend(PlanState *pstate) * Compare the tuples in the two given slots. */ static int32 -heap_compare_slots(Datum a, Datum b, void *arg) +heap_compare_slots(void *a, void *b, void *arg) { MergeAppendState *node = (MergeAppendState *) arg; - SlotNumber slot1 = DatumGetInt32(a); - SlotNumber slot2 = DatumGetInt32(b); + SlotNumber slot1 = *((int *) a); + SlotNumber slot2 = *((int *) b); TupleTableSlot *s1 = node->ms_slots[slot1]; TupleTableSlot *s2 = node->ms_slots[slot2]; diff --git a/src/backend/lib/binaryheap.c b/src/backend/lib/binaryheap.c index 1737546757..0dc0e18f48 100644 --- a/src/backend/lib/binaryheap.c +++ b/src/backend/lib/binaryheap.c @@ -20,6 +20,9 @@ static void sift_down(binaryheap *heap, int node_off); static void sift_up(binaryheap *heap, int node_off); +#define bh_node_ptr(n) (&heap->bh_nodes[(n) * heap->bh_elem_size]) +#define bh_set(n, d) (memmove(bh_node_ptr((n)), (d), heap->bh_elem_size)) + /* * binaryheap_allocate * @@ -29,14 +32,16 @@ static void sift_up(binaryheap *heap, int node_off); * argument specified by 'arg'. */ binaryheap * -binaryheap_allocate(int capacity, binaryheap_comparator compare, void *arg) +binaryheap_allocate(int capacity, size_t elem_size, + binaryheap_comparator compare, void *arg) { int sz; binaryheap *heap; - sz = offsetof(binaryheap, bh_nodes) + sizeof(Datum) * capacity; + sz = offsetof(binaryheap, bh_nodes) + elem_size * capacity; heap = (binaryheap *) palloc(sz); heap->bh_space = capacity; + heap->bh_elem_size = elem_size; heap->bh_compare = compare; heap->bh_arg = arg; @@ -106,12 +111,12 @@ parent_offset(int i) * afterwards. */ void -binaryheap_add_unordered(binaryheap *heap, Datum d) +binaryheap_add_unordered(binaryheap *heap, void *d) { if (heap->bh_size >= heap->bh_space) elog(ERROR, "out of binary heap slots"); heap->bh_has_heap_property = false; - heap->bh_nodes[heap->bh_size] = d; + bh_set(heap->bh_size, d); heap->bh_size++; } @@ -138,11 +143,11 @@ binaryheap_build(binaryheap *heap) * the heap property. */ void -binaryheap_add(binaryheap *heap, Datum d) +binaryheap_add(binaryheap *heap, void *d) { if (heap->bh_size >= heap->bh_space) elog(ERROR, "out of binary heap slots"); - heap->bh_nodes[heap->bh_size] = d; + bh_set(heap->bh_size, d); heap->bh_size++; sift_up(heap, heap->bh_size - 1); } @@ -154,11 +159,11 @@ binaryheap_add(binaryheap *heap, Datum d) * without modifying the heap. The caller must ensure that this * routine is not used on an empty heap. Always O(1). */ -Datum -binaryheap_first(binaryheap *heap) +void +binaryheap_first(binaryheap *heap, void *result) { Assert(!binaryheap_empty(heap) && heap->bh_has_heap_property); - return heap->bh_nodes[0]; + memmove(result, heap->bh_nodes, heap->bh_elem_size); } /* @@ -169,31 +174,27 @@ binaryheap_first(binaryheap *heap) * that this routine is not used on an empty heap. O(log n) worst * case. */ -Datum -binaryheap_remove_first(binaryheap *heap) +void +binaryheap_remove_first(binaryheap *heap, void *result) { - Datum result; - Assert(!binaryheap_empty(heap) && heap->bh_has_heap_property); /* extract the root node, which will be the result */ - result = heap->bh_nodes[0]; + memmove(result, heap->bh_nodes, heap->bh_elem_size); /* easy if heap contains one element */ if (heap->bh_size == 1) { heap->bh_size--; - return result; + return; } /* * Remove the last node, placing it in the vacated root entry, and sift * the new root node down to its correct position. */ - heap->bh_nodes[0] = heap->bh_nodes[--heap->bh_size]; + bh_set(0, bh_node_ptr(--heap->bh_size)); sift_down(heap, 0); - - return result; } /* @@ -204,11 +205,11 @@ binaryheap_remove_first(binaryheap *heap) * sifting the new node down. */ void -binaryheap_replace_first(binaryheap *heap, Datum d) +binaryheap_replace_first(binaryheap *heap, void *d) { Assert(!binaryheap_empty(heap) && heap->bh_has_heap_property); - heap->bh_nodes[0] = d; + bh_set(0, d); if (heap->bh_size > 1) sift_down(heap, 0); @@ -221,7 +222,9 @@ binaryheap_replace_first(binaryheap *heap, Datum d) static void sift_up(binaryheap *heap, int node_off) { - Datum node_val = heap->bh_nodes[node_off]; + char *node_val = palloc(heap->bh_elem_size); + + memmove(node_val, bh_node_ptr(node_off), heap->bh_elem_size); /* * Within the loop, the node_off'th array entry is a "hole" that @@ -232,14 +235,14 @@ sift_up(binaryheap *heap, int node_off) { int cmp; int parent_off; - Datum parent_val; + void *parent_val; /* * If this node is smaller than its parent, the heap condition is * satisfied, and we're done. */ parent_off = parent_offset(node_off); - parent_val = heap->bh_nodes[parent_off]; + parent_val = bh_node_ptr(parent_off); cmp = heap->bh_compare(node_val, parent_val, heap->bh_arg); @@ -250,11 +253,12 @@ sift_up(binaryheap *heap, int node_off) * Otherwise, swap the parent value with the hole, and go on to check * the node's new parent. */ - heap->bh_nodes[node_off] = parent_val; + bh_set(node_off, parent_val); node_off = parent_off; } /* Re-fill the hole */ - heap->bh_nodes[node_off] = node_val; + bh_set(node_off, node_val); + pfree(node_val); } /* @@ -264,7 +268,9 @@ sift_up(binaryheap *heap, int node_off) static void sift_down(binaryheap *heap, int node_off) { - Datum node_val = heap->bh_nodes[node_off]; + char *node_val = palloc(heap->bh_elem_size); + + memmove(node_val, bh_node_ptr(node_off), heap->bh_elem_size); /* * Within the loop, the node_off'th array entry is a "hole" that @@ -280,20 +286,20 @@ sift_down(binaryheap *heap, int node_off) /* Is the left child larger than the parent? */ if (left_off < heap->bh_size && heap->bh_compare(node_val, - heap->bh_nodes[left_off], + bh_node_ptr(left_off), heap->bh_arg) < 0) swap_off = left_off; /* Is the right child larger than the parent? */ if (right_off < heap->bh_size && heap->bh_compare(node_val, - heap->bh_nodes[right_off], + bh_node_ptr(right_off), heap->bh_arg) < 0) { /* swap with the larger child */ if (!swap_off || - heap->bh_compare(heap->bh_nodes[left_off], - heap->bh_nodes[right_off], + heap->bh_compare(bh_node_ptr(left_off), + bh_node_ptr(right_off), heap->bh_arg) < 0) swap_off = right_off; } @@ -309,9 +315,10 @@ sift_down(binaryheap *heap, int node_off) * Otherwise, swap the hole with the child that violates the heap * property; then go on to check its children. */ - heap->bh_nodes[node_off] = heap->bh_nodes[swap_off]; + bh_set(node_off, bh_node_ptr(swap_off)); node_off = swap_off; } /* Re-fill the hole */ - heap->bh_nodes[node_off] = node_val; + bh_set(node_off, node_val); + pfree(node_val); } diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c index 46af349564..a11005708a 100644 --- a/src/backend/postmaster/pgarch.c +++ b/src/backend/postmaster/pgarch.c @@ -145,7 +145,7 @@ static bool pgarch_readyXlog(char *xlog); static void pgarch_archiveDone(char *xlog); static void pgarch_die(int code, Datum arg); static void HandlePgArchInterrupts(void); -static int ready_file_comparator(Datum a, Datum b, void *arg); +static int ready_file_comparator(void *a, void *b, void *arg); static void LoadArchiveLibrary(void); static void pgarch_call_module_shutdown_cb(int code, Datum arg); @@ -249,7 +249,7 @@ PgArchiverMain(void) arch_files->arch_files_size = 0; /* Initialize our max-heap for prioritizing files to archive. */ - arch_files->arch_heap = binaryheap_allocate(NUM_FILES_PER_DIRECTORY_SCAN, + arch_files->arch_heap = binaryheap_allocate(NUM_FILES_PER_DIRECTORY_SCAN, sizeof(char *), ready_file_comparator, NULL); /* Load the archive_library. */ @@ -631,22 +631,27 @@ pgarch_readyXlog(char *xlog) /* If the heap isn't full yet, quickly add it. */ arch_file = arch_files->arch_filenames[arch_files->arch_heap->bh_size]; strcpy(arch_file, basename); - binaryheap_add_unordered(arch_files->arch_heap, CStringGetDatum(arch_file)); + binaryheap_add_unordered(arch_files->arch_heap, &arch_file); /* If we just filled the heap, make it a valid one. */ if (arch_files->arch_heap->bh_size == NUM_FILES_PER_DIRECTORY_SCAN) binaryheap_build(arch_files->arch_heap); } - else if (ready_file_comparator(binaryheap_first(arch_files->arch_heap), - CStringGetDatum(basename), NULL) > 0) + else { - /* - * Remove the lowest priority file and add the current one to the - * heap. - */ - arch_file = DatumGetCString(binaryheap_remove_first(arch_files->arch_heap)); - strcpy(arch_file, basename); - binaryheap_add(arch_files->arch_heap, CStringGetDatum(arch_file)); + char *result; + + binaryheap_first(arch_files->arch_heap, &result); + if (ready_file_comparator(&result, &basename, NULL) > 0) + { + /* + * Remove the lowest priority file and add the current one to + * the heap. + */ + binaryheap_remove_first(arch_files->arch_heap, &arch_file); + strcpy(arch_file, basename); + binaryheap_add(arch_files->arch_heap, &arch_file); + } } } FreeDir(rldir); @@ -668,7 +673,7 @@ pgarch_readyXlog(char *xlog) */ arch_files->arch_files_size = arch_files->arch_heap->bh_size; for (int i = 0; i < arch_files->arch_files_size; i++) - arch_files->arch_files[i] = DatumGetCString(binaryheap_remove_first(arch_files->arch_heap)); + binaryheap_remove_first(arch_files->arch_heap, &arch_files->arch_files[i]); /* Return the highest priority file. */ arch_files->arch_files_size--; @@ -686,10 +691,10 @@ pgarch_readyXlog(char *xlog) * If "a" and "b" have equivalent values, 0 will be returned. */ static int -ready_file_comparator(Datum a, Datum b, void *arg) +ready_file_comparator(void *a, void *b, void *arg) { - char *a_str = DatumGetCString(a); - char *b_str = DatumGetCString(b); + char *a_str = *((char **) a); + char *b_str = *((char **) b); bool a_history = IsTLHistoryFileName(a_str); bool b_history = IsTLHistoryFileName(b_str); diff --git a/src/backend/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c index 12edc5772a..b0a84c5ed1 100644 --- a/src/backend/replication/logical/reorderbuffer.c +++ b/src/backend/replication/logical/reorderbuffer.c @@ -1223,11 +1223,11 @@ ReorderBufferCommitChild(ReorderBuffer *rb, TransactionId xid, * Binary heap comparison function. */ static int -ReorderBufferIterCompare(Datum a, Datum b, void *arg) +ReorderBufferIterCompare(void *a, void *b, void *arg) { ReorderBufferIterTXNState *state = (ReorderBufferIterTXNState *) arg; - XLogRecPtr pos_a = state->entries[DatumGetInt32(a)].lsn; - XLogRecPtr pos_b = state->entries[DatumGetInt32(b)].lsn; + XLogRecPtr pos_a = state->entries[*((int *) a)].lsn; + XLogRecPtr pos_b = state->entries[*((int *) b)].lsn; if (pos_a < pos_b) return 1; @@ -1296,7 +1296,7 @@ ReorderBufferIterTXNInit(ReorderBuffer *rb, ReorderBufferTXN *txn, } /* allocate heap */ - state->heap = binaryheap_allocate(state->nr_txns, + state->heap = binaryheap_allocate(state->nr_txns, sizeof(int), ReorderBufferIterCompare, state); @@ -1330,7 +1330,8 @@ ReorderBufferIterTXNInit(ReorderBuffer *rb, ReorderBufferTXN *txn, state->entries[off].change = cur_change; state->entries[off].txn = txn; - binaryheap_add_unordered(state->heap, Int32GetDatum(off++)); + binaryheap_add_unordered(state->heap, &off); + off++; } /* add subtransactions if they contain changes */ @@ -1359,7 +1360,8 @@ ReorderBufferIterTXNInit(ReorderBuffer *rb, ReorderBufferTXN *txn, state->entries[off].change = cur_change; state->entries[off].txn = cur_txn; - binaryheap_add_unordered(state->heap, Int32GetDatum(off++)); + binaryheap_add_unordered(state->heap, &off); + off++; } } @@ -1384,7 +1386,7 @@ ReorderBufferIterTXNNext(ReorderBuffer *rb, ReorderBufferIterTXNState *state) if (state->heap->bh_size == 0) return NULL; - off = DatumGetInt32(binaryheap_first(state->heap)); + binaryheap_first(state->heap, &off); entry = &state->entries[off]; /* free memory we might have "leaked" in the previous *Next call */ @@ -1414,7 +1416,7 @@ ReorderBufferIterTXNNext(ReorderBuffer *rb, ReorderBufferIterTXNState *state) state->entries[off].lsn = next_change->lsn; state->entries[off].change = next_change; - binaryheap_replace_first(state->heap, Int32GetDatum(off)); + binaryheap_replace_first(state->heap, &off); return change; } @@ -1450,14 +1452,14 @@ ReorderBufferIterTXNNext(ReorderBuffer *rb, ReorderBufferIterTXNState *state) /* txn stays the same */ state->entries[off].lsn = next_change->lsn; state->entries[off].change = next_change; - binaryheap_replace_first(state->heap, Int32GetDatum(off)); + binaryheap_replace_first(state->heap, &off); return change; } } /* ok, no changes there anymore, remove */ - binaryheap_remove_first(state->heap); + binaryheap_remove_first(state->heap, &off); return change; } diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 3bd82dbfca..5bd7176b3c 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -501,7 +501,7 @@ static void CheckForBufferLeaks(void); static int rlocator_comparator(const void *p1, const void *p2); static inline int buffertag_comparator(const BufferTag *ba, const BufferTag *bb); static inline int ckpt_buforder_comparator(const CkptSortItem *a, const CkptSortItem *b); -static int ts_ckpt_progress_comparator(Datum a, Datum b, void *arg); +static int ts_ckpt_progress_comparator(void *a, void *b, void *arg); /* @@ -2639,7 +2639,7 @@ BufferSync(int flags) * and compute how large a portion of the total progress a single * processed buffer is. */ - ts_heap = binaryheap_allocate(num_spaces, + ts_heap = binaryheap_allocate(num_spaces, sizeof(CkptTsStatus *), ts_ckpt_progress_comparator, NULL); @@ -2649,7 +2649,7 @@ BufferSync(int flags) ts_stat->progress_slice = (float8) num_to_scan / ts_stat->num_to_scan; - binaryheap_add_unordered(ts_heap, PointerGetDatum(ts_stat)); + binaryheap_add_unordered(ts_heap, &ts_stat); } binaryheap_build(ts_heap); @@ -2665,8 +2665,9 @@ BufferSync(int flags) while (!binaryheap_empty(ts_heap)) { BufferDesc *bufHdr = NULL; - CkptTsStatus *ts_stat = (CkptTsStatus *) - DatumGetPointer(binaryheap_first(ts_heap)); + CkptTsStatus *ts_stat; + + binaryheap_first(ts_heap, &ts_stat); buf_id = CkptBufferIds[ts_stat->index].buf_id; Assert(buf_id != -1); @@ -2708,12 +2709,12 @@ BufferSync(int flags) /* Have all the buffers from the tablespace been processed? */ if (ts_stat->num_scanned == ts_stat->num_to_scan) { - binaryheap_remove_first(ts_heap); + binaryheap_remove_first(ts_heap, &ts_stat); } else { /* update heap with the new progress */ - binaryheap_replace_first(ts_heap, PointerGetDatum(ts_stat)); + binaryheap_replace_first(ts_heap, &ts_stat); } /* @@ -5416,10 +5417,10 @@ ckpt_buforder_comparator(const CkptSortItem *a, const CkptSortItem *b) * progress. */ static int -ts_ckpt_progress_comparator(Datum a, Datum b, void *arg) +ts_ckpt_progress_comparator(void *a, void *b, void *arg) { - CkptTsStatus *sa = (CkptTsStatus *) a; - CkptTsStatus *sb = (CkptTsStatus *) b; + CkptTsStatus *sa = *((CkptTsStatus **) a); + CkptTsStatus *sb = *((CkptTsStatus **) b); /* we want a min-heap, so return 1 for the a < b */ if (sa->progress < sb->progress) diff --git a/src/include/lib/binaryheap.h b/src/include/lib/binaryheap.h index 52f7b06b25..24228e5b4b 100644 --- a/src/include/lib/binaryheap.h +++ b/src/include/lib/binaryheap.h @@ -15,7 +15,7 @@ * For a max-heap, the comparator must return <0 iff a < b, 0 iff a == b, * and >0 iff a > b. For a min-heap, the conditions are reversed. */ -typedef int (*binaryheap_comparator) (Datum a, Datum b, void *arg); +typedef int (*binaryheap_comparator) (void *a, void *b, void *arg); /* * binaryheap @@ -31,23 +31,24 @@ typedef struct binaryheap { int bh_size; int bh_space; + size_t bh_elem_size; bool bh_has_heap_property; /* debugging cross-check */ binaryheap_comparator bh_compare; void *bh_arg; - Datum bh_nodes[FLEXIBLE_ARRAY_MEMBER]; + char bh_nodes[FLEXIBLE_ARRAY_MEMBER]; } binaryheap; -extern binaryheap *binaryheap_allocate(int capacity, +extern binaryheap *binaryheap_allocate(int capacity, size_t elem_size, binaryheap_comparator compare, void *arg); extern void binaryheap_reset(binaryheap *heap); extern void binaryheap_free(binaryheap *heap); -extern void binaryheap_add_unordered(binaryheap *heap, Datum d); +extern void binaryheap_add_unordered(binaryheap *heap, void *d); extern void binaryheap_build(binaryheap *heap); -extern void binaryheap_add(binaryheap *heap, Datum d); -extern Datum binaryheap_first(binaryheap *heap); -extern Datum binaryheap_remove_first(binaryheap *heap); -extern void binaryheap_replace_first(binaryheap *heap, Datum d); +extern void binaryheap_add(binaryheap *heap, void *d); +extern void binaryheap_first(binaryheap *heap, void *result); +extern void binaryheap_remove_first(binaryheap *heap, void *result); +extern void binaryheap_replace_first(binaryheap *heap, void *d); #define binaryheap_empty(h) ((h)->bh_size == 0) -- 2.25.1 --fUYQa+Pmc3FrFX/N-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH 1/1] allow binaryheap to use any type @ 2023-09-02 18:48 Nathan Bossart <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nathan Bossart @ 2023-09-02 18:48 UTC (permalink / raw) --- src/backend/executor/nodeGatherMerge.c | 20 +++--- src/backend/executor/nodeMergeAppend.c | 20 +++--- src/backend/lib/binaryheap.c | 71 ++++++++++--------- src/backend/postmaster/pgarch.c | 37 +++++----- .../replication/logical/reorderbuffer.c | 22 +++--- src/backend/storage/buffer/bufmgr.c | 21 +++--- src/include/lib/binaryheap.h | 17 ++--- 7 files changed, 112 insertions(+), 96 deletions(-) diff --git a/src/backend/executor/nodeGatherMerge.c b/src/backend/executor/nodeGatherMerge.c index 9d5e1a46e9..4c75d224c8 100644 --- a/src/backend/executor/nodeGatherMerge.c +++ b/src/backend/executor/nodeGatherMerge.c @@ -52,7 +52,7 @@ typedef struct GMReaderTupleBuffer } GMReaderTupleBuffer; static TupleTableSlot *ExecGatherMerge(PlanState *pstate); -static int32 heap_compare_slots(Datum a, Datum b, void *arg); +static int32 heap_compare_slots(void *a, void *b, void *arg); static TupleTableSlot *gather_merge_getnext(GatherMergeState *gm_state); static MinimalTuple gm_readnext_tuple(GatherMergeState *gm_state, int nreader, bool nowait, bool *done); @@ -428,7 +428,7 @@ gather_merge_setup(GatherMergeState *gm_state) } /* Allocate the resources for the merge */ - gm_state->gm_heap = binaryheap_allocate(nreaders + 1, + gm_state->gm_heap = binaryheap_allocate(nreaders + 1, sizeof(int), heap_compare_slots, gm_state); } @@ -489,7 +489,7 @@ reread: /* Don't have a tuple yet, try to get one */ if (gather_merge_readnext(gm_state, i, nowait)) binaryheap_add_unordered(gm_state->gm_heap, - Int32GetDatum(i)); + &i); } else { @@ -565,14 +565,14 @@ gather_merge_getnext(GatherMergeState *gm_state) * the heap, because it might now compare differently against the * other elements of the heap. */ - i = DatumGetInt32(binaryheap_first(gm_state->gm_heap)); + binaryheap_first(gm_state->gm_heap, &i); if (gather_merge_readnext(gm_state, i, false)) - binaryheap_replace_first(gm_state->gm_heap, Int32GetDatum(i)); + binaryheap_replace_first(gm_state->gm_heap, &i); else { /* reader exhausted, remove it from heap */ - (void) binaryheap_remove_first(gm_state->gm_heap); + binaryheap_remove_first(gm_state->gm_heap, &i); } } @@ -585,7 +585,7 @@ gather_merge_getnext(GatherMergeState *gm_state) else { /* Return next tuple from whichever participant has the leading one */ - i = DatumGetInt32(binaryheap_first(gm_state->gm_heap)); + binaryheap_first(gm_state->gm_heap, &i); return gm_state->gm_slots[i]; } } @@ -750,11 +750,11 @@ typedef int32 SlotNumber; * Compare the tuples in the two given slots. */ static int32 -heap_compare_slots(Datum a, Datum b, void *arg) +heap_compare_slots(void *a, void *b, void *arg) { GatherMergeState *node = (GatherMergeState *) arg; - SlotNumber slot1 = DatumGetInt32(a); - SlotNumber slot2 = DatumGetInt32(b); + SlotNumber slot1 = *((int *) a); + SlotNumber slot2 = *((int *) b); TupleTableSlot *s1 = node->gm_slots[slot1]; TupleTableSlot *s2 = node->gm_slots[slot2]; diff --git a/src/backend/executor/nodeMergeAppend.c b/src/backend/executor/nodeMergeAppend.c index 21b5726e6e..928b4b3719 100644 --- a/src/backend/executor/nodeMergeAppend.c +++ b/src/backend/executor/nodeMergeAppend.c @@ -52,7 +52,7 @@ typedef int32 SlotNumber; static TupleTableSlot *ExecMergeAppend(PlanState *pstate); -static int heap_compare_slots(Datum a, Datum b, void *arg); +static int heap_compare_slots(void *a, void *b, void *arg); /* ---------------------------------------------------------------- @@ -125,7 +125,7 @@ ExecInitMergeAppend(MergeAppend *node, EState *estate, int eflags) mergestate->ms_nplans = nplans; mergestate->ms_slots = (TupleTableSlot **) palloc0(sizeof(TupleTableSlot *) * nplans); - mergestate->ms_heap = binaryheap_allocate(nplans, heap_compare_slots, + mergestate->ms_heap = binaryheap_allocate(nplans, sizeof(int), heap_compare_slots, mergestate); /* @@ -229,7 +229,7 @@ ExecMergeAppend(PlanState *pstate) { node->ms_slots[i] = ExecProcNode(node->mergeplans[i]); if (!TupIsNull(node->ms_slots[i])) - binaryheap_add_unordered(node->ms_heap, Int32GetDatum(i)); + binaryheap_add_unordered(node->ms_heap, &i); } binaryheap_build(node->ms_heap); node->ms_initialized = true; @@ -244,12 +244,12 @@ ExecMergeAppend(PlanState *pstate) * by doing this before returning from the prior call, but it's better * to not pull tuples until necessary.) */ - i = DatumGetInt32(binaryheap_first(node->ms_heap)); + binaryheap_first(node->ms_heap, &i); node->ms_slots[i] = ExecProcNode(node->mergeplans[i]); if (!TupIsNull(node->ms_slots[i])) - binaryheap_replace_first(node->ms_heap, Int32GetDatum(i)); + binaryheap_replace_first(node->ms_heap, &i); else - (void) binaryheap_remove_first(node->ms_heap); + binaryheap_remove_first(node->ms_heap, &i); } if (binaryheap_empty(node->ms_heap)) @@ -259,7 +259,7 @@ ExecMergeAppend(PlanState *pstate) } else { - i = DatumGetInt32(binaryheap_first(node->ms_heap)); + binaryheap_first(node->ms_heap, &i); result = node->ms_slots[i]; } @@ -270,11 +270,11 @@ ExecMergeAppend(PlanState *pstate) * Compare the tuples in the two given slots. */ static int32 -heap_compare_slots(Datum a, Datum b, void *arg) +heap_compare_slots(void *a, void *b, void *arg) { MergeAppendState *node = (MergeAppendState *) arg; - SlotNumber slot1 = DatumGetInt32(a); - SlotNumber slot2 = DatumGetInt32(b); + SlotNumber slot1 = *((int *) a); + SlotNumber slot2 = *((int *) b); TupleTableSlot *s1 = node->ms_slots[slot1]; TupleTableSlot *s2 = node->ms_slots[slot2]; diff --git a/src/backend/lib/binaryheap.c b/src/backend/lib/binaryheap.c index 1737546757..0dc0e18f48 100644 --- a/src/backend/lib/binaryheap.c +++ b/src/backend/lib/binaryheap.c @@ -20,6 +20,9 @@ static void sift_down(binaryheap *heap, int node_off); static void sift_up(binaryheap *heap, int node_off); +#define bh_node_ptr(n) (&heap->bh_nodes[(n) * heap->bh_elem_size]) +#define bh_set(n, d) (memmove(bh_node_ptr((n)), (d), heap->bh_elem_size)) + /* * binaryheap_allocate * @@ -29,14 +32,16 @@ static void sift_up(binaryheap *heap, int node_off); * argument specified by 'arg'. */ binaryheap * -binaryheap_allocate(int capacity, binaryheap_comparator compare, void *arg) +binaryheap_allocate(int capacity, size_t elem_size, + binaryheap_comparator compare, void *arg) { int sz; binaryheap *heap; - sz = offsetof(binaryheap, bh_nodes) + sizeof(Datum) * capacity; + sz = offsetof(binaryheap, bh_nodes) + elem_size * capacity; heap = (binaryheap *) palloc(sz); heap->bh_space = capacity; + heap->bh_elem_size = elem_size; heap->bh_compare = compare; heap->bh_arg = arg; @@ -106,12 +111,12 @@ parent_offset(int i) * afterwards. */ void -binaryheap_add_unordered(binaryheap *heap, Datum d) +binaryheap_add_unordered(binaryheap *heap, void *d) { if (heap->bh_size >= heap->bh_space) elog(ERROR, "out of binary heap slots"); heap->bh_has_heap_property = false; - heap->bh_nodes[heap->bh_size] = d; + bh_set(heap->bh_size, d); heap->bh_size++; } @@ -138,11 +143,11 @@ binaryheap_build(binaryheap *heap) * the heap property. */ void -binaryheap_add(binaryheap *heap, Datum d) +binaryheap_add(binaryheap *heap, void *d) { if (heap->bh_size >= heap->bh_space) elog(ERROR, "out of binary heap slots"); - heap->bh_nodes[heap->bh_size] = d; + bh_set(heap->bh_size, d); heap->bh_size++; sift_up(heap, heap->bh_size - 1); } @@ -154,11 +159,11 @@ binaryheap_add(binaryheap *heap, Datum d) * without modifying the heap. The caller must ensure that this * routine is not used on an empty heap. Always O(1). */ -Datum -binaryheap_first(binaryheap *heap) +void +binaryheap_first(binaryheap *heap, void *result) { Assert(!binaryheap_empty(heap) && heap->bh_has_heap_property); - return heap->bh_nodes[0]; + memmove(result, heap->bh_nodes, heap->bh_elem_size); } /* @@ -169,31 +174,27 @@ binaryheap_first(binaryheap *heap) * that this routine is not used on an empty heap. O(log n) worst * case. */ -Datum -binaryheap_remove_first(binaryheap *heap) +void +binaryheap_remove_first(binaryheap *heap, void *result) { - Datum result; - Assert(!binaryheap_empty(heap) && heap->bh_has_heap_property); /* extract the root node, which will be the result */ - result = heap->bh_nodes[0]; + memmove(result, heap->bh_nodes, heap->bh_elem_size); /* easy if heap contains one element */ if (heap->bh_size == 1) { heap->bh_size--; - return result; + return; } /* * Remove the last node, placing it in the vacated root entry, and sift * the new root node down to its correct position. */ - heap->bh_nodes[0] = heap->bh_nodes[--heap->bh_size]; + bh_set(0, bh_node_ptr(--heap->bh_size)); sift_down(heap, 0); - - return result; } /* @@ -204,11 +205,11 @@ binaryheap_remove_first(binaryheap *heap) * sifting the new node down. */ void -binaryheap_replace_first(binaryheap *heap, Datum d) +binaryheap_replace_first(binaryheap *heap, void *d) { Assert(!binaryheap_empty(heap) && heap->bh_has_heap_property); - heap->bh_nodes[0] = d; + bh_set(0, d); if (heap->bh_size > 1) sift_down(heap, 0); @@ -221,7 +222,9 @@ binaryheap_replace_first(binaryheap *heap, Datum d) static void sift_up(binaryheap *heap, int node_off) { - Datum node_val = heap->bh_nodes[node_off]; + char *node_val = palloc(heap->bh_elem_size); + + memmove(node_val, bh_node_ptr(node_off), heap->bh_elem_size); /* * Within the loop, the node_off'th array entry is a "hole" that @@ -232,14 +235,14 @@ sift_up(binaryheap *heap, int node_off) { int cmp; int parent_off; - Datum parent_val; + void *parent_val; /* * If this node is smaller than its parent, the heap condition is * satisfied, and we're done. */ parent_off = parent_offset(node_off); - parent_val = heap->bh_nodes[parent_off]; + parent_val = bh_node_ptr(parent_off); cmp = heap->bh_compare(node_val, parent_val, heap->bh_arg); @@ -250,11 +253,12 @@ sift_up(binaryheap *heap, int node_off) * Otherwise, swap the parent value with the hole, and go on to check * the node's new parent. */ - heap->bh_nodes[node_off] = parent_val; + bh_set(node_off, parent_val); node_off = parent_off; } /* Re-fill the hole */ - heap->bh_nodes[node_off] = node_val; + bh_set(node_off, node_val); + pfree(node_val); } /* @@ -264,7 +268,9 @@ sift_up(binaryheap *heap, int node_off) static void sift_down(binaryheap *heap, int node_off) { - Datum node_val = heap->bh_nodes[node_off]; + char *node_val = palloc(heap->bh_elem_size); + + memmove(node_val, bh_node_ptr(node_off), heap->bh_elem_size); /* * Within the loop, the node_off'th array entry is a "hole" that @@ -280,20 +286,20 @@ sift_down(binaryheap *heap, int node_off) /* Is the left child larger than the parent? */ if (left_off < heap->bh_size && heap->bh_compare(node_val, - heap->bh_nodes[left_off], + bh_node_ptr(left_off), heap->bh_arg) < 0) swap_off = left_off; /* Is the right child larger than the parent? */ if (right_off < heap->bh_size && heap->bh_compare(node_val, - heap->bh_nodes[right_off], + bh_node_ptr(right_off), heap->bh_arg) < 0) { /* swap with the larger child */ if (!swap_off || - heap->bh_compare(heap->bh_nodes[left_off], - heap->bh_nodes[right_off], + heap->bh_compare(bh_node_ptr(left_off), + bh_node_ptr(right_off), heap->bh_arg) < 0) swap_off = right_off; } @@ -309,9 +315,10 @@ sift_down(binaryheap *heap, int node_off) * Otherwise, swap the hole with the child that violates the heap * property; then go on to check its children. */ - heap->bh_nodes[node_off] = heap->bh_nodes[swap_off]; + bh_set(node_off, bh_node_ptr(swap_off)); node_off = swap_off; } /* Re-fill the hole */ - heap->bh_nodes[node_off] = node_val; + bh_set(node_off, node_val); + pfree(node_val); } diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c index 46af349564..a11005708a 100644 --- a/src/backend/postmaster/pgarch.c +++ b/src/backend/postmaster/pgarch.c @@ -145,7 +145,7 @@ static bool pgarch_readyXlog(char *xlog); static void pgarch_archiveDone(char *xlog); static void pgarch_die(int code, Datum arg); static void HandlePgArchInterrupts(void); -static int ready_file_comparator(Datum a, Datum b, void *arg); +static int ready_file_comparator(void *a, void *b, void *arg); static void LoadArchiveLibrary(void); static void pgarch_call_module_shutdown_cb(int code, Datum arg); @@ -249,7 +249,7 @@ PgArchiverMain(void) arch_files->arch_files_size = 0; /* Initialize our max-heap for prioritizing files to archive. */ - arch_files->arch_heap = binaryheap_allocate(NUM_FILES_PER_DIRECTORY_SCAN, + arch_files->arch_heap = binaryheap_allocate(NUM_FILES_PER_DIRECTORY_SCAN, sizeof(char *), ready_file_comparator, NULL); /* Load the archive_library. */ @@ -631,22 +631,27 @@ pgarch_readyXlog(char *xlog) /* If the heap isn't full yet, quickly add it. */ arch_file = arch_files->arch_filenames[arch_files->arch_heap->bh_size]; strcpy(arch_file, basename); - binaryheap_add_unordered(arch_files->arch_heap, CStringGetDatum(arch_file)); + binaryheap_add_unordered(arch_files->arch_heap, &arch_file); /* If we just filled the heap, make it a valid one. */ if (arch_files->arch_heap->bh_size == NUM_FILES_PER_DIRECTORY_SCAN) binaryheap_build(arch_files->arch_heap); } - else if (ready_file_comparator(binaryheap_first(arch_files->arch_heap), - CStringGetDatum(basename), NULL) > 0) + else { - /* - * Remove the lowest priority file and add the current one to the - * heap. - */ - arch_file = DatumGetCString(binaryheap_remove_first(arch_files->arch_heap)); - strcpy(arch_file, basename); - binaryheap_add(arch_files->arch_heap, CStringGetDatum(arch_file)); + char *result; + + binaryheap_first(arch_files->arch_heap, &result); + if (ready_file_comparator(&result, &basename, NULL) > 0) + { + /* + * Remove the lowest priority file and add the current one to + * the heap. + */ + binaryheap_remove_first(arch_files->arch_heap, &arch_file); + strcpy(arch_file, basename); + binaryheap_add(arch_files->arch_heap, &arch_file); + } } } FreeDir(rldir); @@ -668,7 +673,7 @@ pgarch_readyXlog(char *xlog) */ arch_files->arch_files_size = arch_files->arch_heap->bh_size; for (int i = 0; i < arch_files->arch_files_size; i++) - arch_files->arch_files[i] = DatumGetCString(binaryheap_remove_first(arch_files->arch_heap)); + binaryheap_remove_first(arch_files->arch_heap, &arch_files->arch_files[i]); /* Return the highest priority file. */ arch_files->arch_files_size--; @@ -686,10 +691,10 @@ pgarch_readyXlog(char *xlog) * If "a" and "b" have equivalent values, 0 will be returned. */ static int -ready_file_comparator(Datum a, Datum b, void *arg) +ready_file_comparator(void *a, void *b, void *arg) { - char *a_str = DatumGetCString(a); - char *b_str = DatumGetCString(b); + char *a_str = *((char **) a); + char *b_str = *((char **) b); bool a_history = IsTLHistoryFileName(a_str); bool b_history = IsTLHistoryFileName(b_str); diff --git a/src/backend/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c index 12edc5772a..b0a84c5ed1 100644 --- a/src/backend/replication/logical/reorderbuffer.c +++ b/src/backend/replication/logical/reorderbuffer.c @@ -1223,11 +1223,11 @@ ReorderBufferCommitChild(ReorderBuffer *rb, TransactionId xid, * Binary heap comparison function. */ static int -ReorderBufferIterCompare(Datum a, Datum b, void *arg) +ReorderBufferIterCompare(void *a, void *b, void *arg) { ReorderBufferIterTXNState *state = (ReorderBufferIterTXNState *) arg; - XLogRecPtr pos_a = state->entries[DatumGetInt32(a)].lsn; - XLogRecPtr pos_b = state->entries[DatumGetInt32(b)].lsn; + XLogRecPtr pos_a = state->entries[*((int *) a)].lsn; + XLogRecPtr pos_b = state->entries[*((int *) b)].lsn; if (pos_a < pos_b) return 1; @@ -1296,7 +1296,7 @@ ReorderBufferIterTXNInit(ReorderBuffer *rb, ReorderBufferTXN *txn, } /* allocate heap */ - state->heap = binaryheap_allocate(state->nr_txns, + state->heap = binaryheap_allocate(state->nr_txns, sizeof(int), ReorderBufferIterCompare, state); @@ -1330,7 +1330,8 @@ ReorderBufferIterTXNInit(ReorderBuffer *rb, ReorderBufferTXN *txn, state->entries[off].change = cur_change; state->entries[off].txn = txn; - binaryheap_add_unordered(state->heap, Int32GetDatum(off++)); + binaryheap_add_unordered(state->heap, &off); + off++; } /* add subtransactions if they contain changes */ @@ -1359,7 +1360,8 @@ ReorderBufferIterTXNInit(ReorderBuffer *rb, ReorderBufferTXN *txn, state->entries[off].change = cur_change; state->entries[off].txn = cur_txn; - binaryheap_add_unordered(state->heap, Int32GetDatum(off++)); + binaryheap_add_unordered(state->heap, &off); + off++; } } @@ -1384,7 +1386,7 @@ ReorderBufferIterTXNNext(ReorderBuffer *rb, ReorderBufferIterTXNState *state) if (state->heap->bh_size == 0) return NULL; - off = DatumGetInt32(binaryheap_first(state->heap)); + binaryheap_first(state->heap, &off); entry = &state->entries[off]; /* free memory we might have "leaked" in the previous *Next call */ @@ -1414,7 +1416,7 @@ ReorderBufferIterTXNNext(ReorderBuffer *rb, ReorderBufferIterTXNState *state) state->entries[off].lsn = next_change->lsn; state->entries[off].change = next_change; - binaryheap_replace_first(state->heap, Int32GetDatum(off)); + binaryheap_replace_first(state->heap, &off); return change; } @@ -1450,14 +1452,14 @@ ReorderBufferIterTXNNext(ReorderBuffer *rb, ReorderBufferIterTXNState *state) /* txn stays the same */ state->entries[off].lsn = next_change->lsn; state->entries[off].change = next_change; - binaryheap_replace_first(state->heap, Int32GetDatum(off)); + binaryheap_replace_first(state->heap, &off); return change; } } /* ok, no changes there anymore, remove */ - binaryheap_remove_first(state->heap); + binaryheap_remove_first(state->heap, &off); return change; } diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 3bd82dbfca..5bd7176b3c 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -501,7 +501,7 @@ static void CheckForBufferLeaks(void); static int rlocator_comparator(const void *p1, const void *p2); static inline int buffertag_comparator(const BufferTag *ba, const BufferTag *bb); static inline int ckpt_buforder_comparator(const CkptSortItem *a, const CkptSortItem *b); -static int ts_ckpt_progress_comparator(Datum a, Datum b, void *arg); +static int ts_ckpt_progress_comparator(void *a, void *b, void *arg); /* @@ -2639,7 +2639,7 @@ BufferSync(int flags) * and compute how large a portion of the total progress a single * processed buffer is. */ - ts_heap = binaryheap_allocate(num_spaces, + ts_heap = binaryheap_allocate(num_spaces, sizeof(CkptTsStatus *), ts_ckpt_progress_comparator, NULL); @@ -2649,7 +2649,7 @@ BufferSync(int flags) ts_stat->progress_slice = (float8) num_to_scan / ts_stat->num_to_scan; - binaryheap_add_unordered(ts_heap, PointerGetDatum(ts_stat)); + binaryheap_add_unordered(ts_heap, &ts_stat); } binaryheap_build(ts_heap); @@ -2665,8 +2665,9 @@ BufferSync(int flags) while (!binaryheap_empty(ts_heap)) { BufferDesc *bufHdr = NULL; - CkptTsStatus *ts_stat = (CkptTsStatus *) - DatumGetPointer(binaryheap_first(ts_heap)); + CkptTsStatus *ts_stat; + + binaryheap_first(ts_heap, &ts_stat); buf_id = CkptBufferIds[ts_stat->index].buf_id; Assert(buf_id != -1); @@ -2708,12 +2709,12 @@ BufferSync(int flags) /* Have all the buffers from the tablespace been processed? */ if (ts_stat->num_scanned == ts_stat->num_to_scan) { - binaryheap_remove_first(ts_heap); + binaryheap_remove_first(ts_heap, &ts_stat); } else { /* update heap with the new progress */ - binaryheap_replace_first(ts_heap, PointerGetDatum(ts_stat)); + binaryheap_replace_first(ts_heap, &ts_stat); } /* @@ -5416,10 +5417,10 @@ ckpt_buforder_comparator(const CkptSortItem *a, const CkptSortItem *b) * progress. */ static int -ts_ckpt_progress_comparator(Datum a, Datum b, void *arg) +ts_ckpt_progress_comparator(void *a, void *b, void *arg) { - CkptTsStatus *sa = (CkptTsStatus *) a; - CkptTsStatus *sb = (CkptTsStatus *) b; + CkptTsStatus *sa = *((CkptTsStatus **) a); + CkptTsStatus *sb = *((CkptTsStatus **) b); /* we want a min-heap, so return 1 for the a < b */ if (sa->progress < sb->progress) diff --git a/src/include/lib/binaryheap.h b/src/include/lib/binaryheap.h index 52f7b06b25..24228e5b4b 100644 --- a/src/include/lib/binaryheap.h +++ b/src/include/lib/binaryheap.h @@ -15,7 +15,7 @@ * For a max-heap, the comparator must return <0 iff a < b, 0 iff a == b, * and >0 iff a > b. For a min-heap, the conditions are reversed. */ -typedef int (*binaryheap_comparator) (Datum a, Datum b, void *arg); +typedef int (*binaryheap_comparator) (void *a, void *b, void *arg); /* * binaryheap @@ -31,23 +31,24 @@ typedef struct binaryheap { int bh_size; int bh_space; + size_t bh_elem_size; bool bh_has_heap_property; /* debugging cross-check */ binaryheap_comparator bh_compare; void *bh_arg; - Datum bh_nodes[FLEXIBLE_ARRAY_MEMBER]; + char bh_nodes[FLEXIBLE_ARRAY_MEMBER]; } binaryheap; -extern binaryheap *binaryheap_allocate(int capacity, +extern binaryheap *binaryheap_allocate(int capacity, size_t elem_size, binaryheap_comparator compare, void *arg); extern void binaryheap_reset(binaryheap *heap); extern void binaryheap_free(binaryheap *heap); -extern void binaryheap_add_unordered(binaryheap *heap, Datum d); +extern void binaryheap_add_unordered(binaryheap *heap, void *d); extern void binaryheap_build(binaryheap *heap); -extern void binaryheap_add(binaryheap *heap, Datum d); -extern Datum binaryheap_first(binaryheap *heap); -extern Datum binaryheap_remove_first(binaryheap *heap); -extern void binaryheap_replace_first(binaryheap *heap, Datum d); +extern void binaryheap_add(binaryheap *heap, void *d); +extern void binaryheap_first(binaryheap *heap, void *result); +extern void binaryheap_remove_first(binaryheap *heap, void *result); +extern void binaryheap_replace_first(binaryheap *heap, void *d); #define binaryheap_empty(h) ((h)->bh_size == 0) -- 2.25.1 --fUYQa+Pmc3FrFX/N-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH 1/1] allow binaryheap to use any type @ 2023-09-02 18:48 Nathan Bossart <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nathan Bossart @ 2023-09-02 18:48 UTC (permalink / raw) --- src/backend/executor/nodeGatherMerge.c | 20 +++--- src/backend/executor/nodeMergeAppend.c | 20 +++--- src/backend/lib/binaryheap.c | 71 ++++++++++--------- src/backend/postmaster/pgarch.c | 37 +++++----- .../replication/logical/reorderbuffer.c | 22 +++--- src/backend/storage/buffer/bufmgr.c | 21 +++--- src/include/lib/binaryheap.h | 17 ++--- 7 files changed, 112 insertions(+), 96 deletions(-) diff --git a/src/backend/executor/nodeGatherMerge.c b/src/backend/executor/nodeGatherMerge.c index 9d5e1a46e9..4c75d224c8 100644 --- a/src/backend/executor/nodeGatherMerge.c +++ b/src/backend/executor/nodeGatherMerge.c @@ -52,7 +52,7 @@ typedef struct GMReaderTupleBuffer } GMReaderTupleBuffer; static TupleTableSlot *ExecGatherMerge(PlanState *pstate); -static int32 heap_compare_slots(Datum a, Datum b, void *arg); +static int32 heap_compare_slots(void *a, void *b, void *arg); static TupleTableSlot *gather_merge_getnext(GatherMergeState *gm_state); static MinimalTuple gm_readnext_tuple(GatherMergeState *gm_state, int nreader, bool nowait, bool *done); @@ -428,7 +428,7 @@ gather_merge_setup(GatherMergeState *gm_state) } /* Allocate the resources for the merge */ - gm_state->gm_heap = binaryheap_allocate(nreaders + 1, + gm_state->gm_heap = binaryheap_allocate(nreaders + 1, sizeof(int), heap_compare_slots, gm_state); } @@ -489,7 +489,7 @@ reread: /* Don't have a tuple yet, try to get one */ if (gather_merge_readnext(gm_state, i, nowait)) binaryheap_add_unordered(gm_state->gm_heap, - Int32GetDatum(i)); + &i); } else { @@ -565,14 +565,14 @@ gather_merge_getnext(GatherMergeState *gm_state) * the heap, because it might now compare differently against the * other elements of the heap. */ - i = DatumGetInt32(binaryheap_first(gm_state->gm_heap)); + binaryheap_first(gm_state->gm_heap, &i); if (gather_merge_readnext(gm_state, i, false)) - binaryheap_replace_first(gm_state->gm_heap, Int32GetDatum(i)); + binaryheap_replace_first(gm_state->gm_heap, &i); else { /* reader exhausted, remove it from heap */ - (void) binaryheap_remove_first(gm_state->gm_heap); + binaryheap_remove_first(gm_state->gm_heap, &i); } } @@ -585,7 +585,7 @@ gather_merge_getnext(GatherMergeState *gm_state) else { /* Return next tuple from whichever participant has the leading one */ - i = DatumGetInt32(binaryheap_first(gm_state->gm_heap)); + binaryheap_first(gm_state->gm_heap, &i); return gm_state->gm_slots[i]; } } @@ -750,11 +750,11 @@ typedef int32 SlotNumber; * Compare the tuples in the two given slots. */ static int32 -heap_compare_slots(Datum a, Datum b, void *arg) +heap_compare_slots(void *a, void *b, void *arg) { GatherMergeState *node = (GatherMergeState *) arg; - SlotNumber slot1 = DatumGetInt32(a); - SlotNumber slot2 = DatumGetInt32(b); + SlotNumber slot1 = *((int *) a); + SlotNumber slot2 = *((int *) b); TupleTableSlot *s1 = node->gm_slots[slot1]; TupleTableSlot *s2 = node->gm_slots[slot2]; diff --git a/src/backend/executor/nodeMergeAppend.c b/src/backend/executor/nodeMergeAppend.c index 21b5726e6e..928b4b3719 100644 --- a/src/backend/executor/nodeMergeAppend.c +++ b/src/backend/executor/nodeMergeAppend.c @@ -52,7 +52,7 @@ typedef int32 SlotNumber; static TupleTableSlot *ExecMergeAppend(PlanState *pstate); -static int heap_compare_slots(Datum a, Datum b, void *arg); +static int heap_compare_slots(void *a, void *b, void *arg); /* ---------------------------------------------------------------- @@ -125,7 +125,7 @@ ExecInitMergeAppend(MergeAppend *node, EState *estate, int eflags) mergestate->ms_nplans = nplans; mergestate->ms_slots = (TupleTableSlot **) palloc0(sizeof(TupleTableSlot *) * nplans); - mergestate->ms_heap = binaryheap_allocate(nplans, heap_compare_slots, + mergestate->ms_heap = binaryheap_allocate(nplans, sizeof(int), heap_compare_slots, mergestate); /* @@ -229,7 +229,7 @@ ExecMergeAppend(PlanState *pstate) { node->ms_slots[i] = ExecProcNode(node->mergeplans[i]); if (!TupIsNull(node->ms_slots[i])) - binaryheap_add_unordered(node->ms_heap, Int32GetDatum(i)); + binaryheap_add_unordered(node->ms_heap, &i); } binaryheap_build(node->ms_heap); node->ms_initialized = true; @@ -244,12 +244,12 @@ ExecMergeAppend(PlanState *pstate) * by doing this before returning from the prior call, but it's better * to not pull tuples until necessary.) */ - i = DatumGetInt32(binaryheap_first(node->ms_heap)); + binaryheap_first(node->ms_heap, &i); node->ms_slots[i] = ExecProcNode(node->mergeplans[i]); if (!TupIsNull(node->ms_slots[i])) - binaryheap_replace_first(node->ms_heap, Int32GetDatum(i)); + binaryheap_replace_first(node->ms_heap, &i); else - (void) binaryheap_remove_first(node->ms_heap); + binaryheap_remove_first(node->ms_heap, &i); } if (binaryheap_empty(node->ms_heap)) @@ -259,7 +259,7 @@ ExecMergeAppend(PlanState *pstate) } else { - i = DatumGetInt32(binaryheap_first(node->ms_heap)); + binaryheap_first(node->ms_heap, &i); result = node->ms_slots[i]; } @@ -270,11 +270,11 @@ ExecMergeAppend(PlanState *pstate) * Compare the tuples in the two given slots. */ static int32 -heap_compare_slots(Datum a, Datum b, void *arg) +heap_compare_slots(void *a, void *b, void *arg) { MergeAppendState *node = (MergeAppendState *) arg; - SlotNumber slot1 = DatumGetInt32(a); - SlotNumber slot2 = DatumGetInt32(b); + SlotNumber slot1 = *((int *) a); + SlotNumber slot2 = *((int *) b); TupleTableSlot *s1 = node->ms_slots[slot1]; TupleTableSlot *s2 = node->ms_slots[slot2]; diff --git a/src/backend/lib/binaryheap.c b/src/backend/lib/binaryheap.c index 1737546757..0dc0e18f48 100644 --- a/src/backend/lib/binaryheap.c +++ b/src/backend/lib/binaryheap.c @@ -20,6 +20,9 @@ static void sift_down(binaryheap *heap, int node_off); static void sift_up(binaryheap *heap, int node_off); +#define bh_node_ptr(n) (&heap->bh_nodes[(n) * heap->bh_elem_size]) +#define bh_set(n, d) (memmove(bh_node_ptr((n)), (d), heap->bh_elem_size)) + /* * binaryheap_allocate * @@ -29,14 +32,16 @@ static void sift_up(binaryheap *heap, int node_off); * argument specified by 'arg'. */ binaryheap * -binaryheap_allocate(int capacity, binaryheap_comparator compare, void *arg) +binaryheap_allocate(int capacity, size_t elem_size, + binaryheap_comparator compare, void *arg) { int sz; binaryheap *heap; - sz = offsetof(binaryheap, bh_nodes) + sizeof(Datum) * capacity; + sz = offsetof(binaryheap, bh_nodes) + elem_size * capacity; heap = (binaryheap *) palloc(sz); heap->bh_space = capacity; + heap->bh_elem_size = elem_size; heap->bh_compare = compare; heap->bh_arg = arg; @@ -106,12 +111,12 @@ parent_offset(int i) * afterwards. */ void -binaryheap_add_unordered(binaryheap *heap, Datum d) +binaryheap_add_unordered(binaryheap *heap, void *d) { if (heap->bh_size >= heap->bh_space) elog(ERROR, "out of binary heap slots"); heap->bh_has_heap_property = false; - heap->bh_nodes[heap->bh_size] = d; + bh_set(heap->bh_size, d); heap->bh_size++; } @@ -138,11 +143,11 @@ binaryheap_build(binaryheap *heap) * the heap property. */ void -binaryheap_add(binaryheap *heap, Datum d) +binaryheap_add(binaryheap *heap, void *d) { if (heap->bh_size >= heap->bh_space) elog(ERROR, "out of binary heap slots"); - heap->bh_nodes[heap->bh_size] = d; + bh_set(heap->bh_size, d); heap->bh_size++; sift_up(heap, heap->bh_size - 1); } @@ -154,11 +159,11 @@ binaryheap_add(binaryheap *heap, Datum d) * without modifying the heap. The caller must ensure that this * routine is not used on an empty heap. Always O(1). */ -Datum -binaryheap_first(binaryheap *heap) +void +binaryheap_first(binaryheap *heap, void *result) { Assert(!binaryheap_empty(heap) && heap->bh_has_heap_property); - return heap->bh_nodes[0]; + memmove(result, heap->bh_nodes, heap->bh_elem_size); } /* @@ -169,31 +174,27 @@ binaryheap_first(binaryheap *heap) * that this routine is not used on an empty heap. O(log n) worst * case. */ -Datum -binaryheap_remove_first(binaryheap *heap) +void +binaryheap_remove_first(binaryheap *heap, void *result) { - Datum result; - Assert(!binaryheap_empty(heap) && heap->bh_has_heap_property); /* extract the root node, which will be the result */ - result = heap->bh_nodes[0]; + memmove(result, heap->bh_nodes, heap->bh_elem_size); /* easy if heap contains one element */ if (heap->bh_size == 1) { heap->bh_size--; - return result; + return; } /* * Remove the last node, placing it in the vacated root entry, and sift * the new root node down to its correct position. */ - heap->bh_nodes[0] = heap->bh_nodes[--heap->bh_size]; + bh_set(0, bh_node_ptr(--heap->bh_size)); sift_down(heap, 0); - - return result; } /* @@ -204,11 +205,11 @@ binaryheap_remove_first(binaryheap *heap) * sifting the new node down. */ void -binaryheap_replace_first(binaryheap *heap, Datum d) +binaryheap_replace_first(binaryheap *heap, void *d) { Assert(!binaryheap_empty(heap) && heap->bh_has_heap_property); - heap->bh_nodes[0] = d; + bh_set(0, d); if (heap->bh_size > 1) sift_down(heap, 0); @@ -221,7 +222,9 @@ binaryheap_replace_first(binaryheap *heap, Datum d) static void sift_up(binaryheap *heap, int node_off) { - Datum node_val = heap->bh_nodes[node_off]; + char *node_val = palloc(heap->bh_elem_size); + + memmove(node_val, bh_node_ptr(node_off), heap->bh_elem_size); /* * Within the loop, the node_off'th array entry is a "hole" that @@ -232,14 +235,14 @@ sift_up(binaryheap *heap, int node_off) { int cmp; int parent_off; - Datum parent_val; + void *parent_val; /* * If this node is smaller than its parent, the heap condition is * satisfied, and we're done. */ parent_off = parent_offset(node_off); - parent_val = heap->bh_nodes[parent_off]; + parent_val = bh_node_ptr(parent_off); cmp = heap->bh_compare(node_val, parent_val, heap->bh_arg); @@ -250,11 +253,12 @@ sift_up(binaryheap *heap, int node_off) * Otherwise, swap the parent value with the hole, and go on to check * the node's new parent. */ - heap->bh_nodes[node_off] = parent_val; + bh_set(node_off, parent_val); node_off = parent_off; } /* Re-fill the hole */ - heap->bh_nodes[node_off] = node_val; + bh_set(node_off, node_val); + pfree(node_val); } /* @@ -264,7 +268,9 @@ sift_up(binaryheap *heap, int node_off) static void sift_down(binaryheap *heap, int node_off) { - Datum node_val = heap->bh_nodes[node_off]; + char *node_val = palloc(heap->bh_elem_size); + + memmove(node_val, bh_node_ptr(node_off), heap->bh_elem_size); /* * Within the loop, the node_off'th array entry is a "hole" that @@ -280,20 +286,20 @@ sift_down(binaryheap *heap, int node_off) /* Is the left child larger than the parent? */ if (left_off < heap->bh_size && heap->bh_compare(node_val, - heap->bh_nodes[left_off], + bh_node_ptr(left_off), heap->bh_arg) < 0) swap_off = left_off; /* Is the right child larger than the parent? */ if (right_off < heap->bh_size && heap->bh_compare(node_val, - heap->bh_nodes[right_off], + bh_node_ptr(right_off), heap->bh_arg) < 0) { /* swap with the larger child */ if (!swap_off || - heap->bh_compare(heap->bh_nodes[left_off], - heap->bh_nodes[right_off], + heap->bh_compare(bh_node_ptr(left_off), + bh_node_ptr(right_off), heap->bh_arg) < 0) swap_off = right_off; } @@ -309,9 +315,10 @@ sift_down(binaryheap *heap, int node_off) * Otherwise, swap the hole with the child that violates the heap * property; then go on to check its children. */ - heap->bh_nodes[node_off] = heap->bh_nodes[swap_off]; + bh_set(node_off, bh_node_ptr(swap_off)); node_off = swap_off; } /* Re-fill the hole */ - heap->bh_nodes[node_off] = node_val; + bh_set(node_off, node_val); + pfree(node_val); } diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c index 46af349564..a11005708a 100644 --- a/src/backend/postmaster/pgarch.c +++ b/src/backend/postmaster/pgarch.c @@ -145,7 +145,7 @@ static bool pgarch_readyXlog(char *xlog); static void pgarch_archiveDone(char *xlog); static void pgarch_die(int code, Datum arg); static void HandlePgArchInterrupts(void); -static int ready_file_comparator(Datum a, Datum b, void *arg); +static int ready_file_comparator(void *a, void *b, void *arg); static void LoadArchiveLibrary(void); static void pgarch_call_module_shutdown_cb(int code, Datum arg); @@ -249,7 +249,7 @@ PgArchiverMain(void) arch_files->arch_files_size = 0; /* Initialize our max-heap for prioritizing files to archive. */ - arch_files->arch_heap = binaryheap_allocate(NUM_FILES_PER_DIRECTORY_SCAN, + arch_files->arch_heap = binaryheap_allocate(NUM_FILES_PER_DIRECTORY_SCAN, sizeof(char *), ready_file_comparator, NULL); /* Load the archive_library. */ @@ -631,22 +631,27 @@ pgarch_readyXlog(char *xlog) /* If the heap isn't full yet, quickly add it. */ arch_file = arch_files->arch_filenames[arch_files->arch_heap->bh_size]; strcpy(arch_file, basename); - binaryheap_add_unordered(arch_files->arch_heap, CStringGetDatum(arch_file)); + binaryheap_add_unordered(arch_files->arch_heap, &arch_file); /* If we just filled the heap, make it a valid one. */ if (arch_files->arch_heap->bh_size == NUM_FILES_PER_DIRECTORY_SCAN) binaryheap_build(arch_files->arch_heap); } - else if (ready_file_comparator(binaryheap_first(arch_files->arch_heap), - CStringGetDatum(basename), NULL) > 0) + else { - /* - * Remove the lowest priority file and add the current one to the - * heap. - */ - arch_file = DatumGetCString(binaryheap_remove_first(arch_files->arch_heap)); - strcpy(arch_file, basename); - binaryheap_add(arch_files->arch_heap, CStringGetDatum(arch_file)); + char *result; + + binaryheap_first(arch_files->arch_heap, &result); + if (ready_file_comparator(&result, &basename, NULL) > 0) + { + /* + * Remove the lowest priority file and add the current one to + * the heap. + */ + binaryheap_remove_first(arch_files->arch_heap, &arch_file); + strcpy(arch_file, basename); + binaryheap_add(arch_files->arch_heap, &arch_file); + } } } FreeDir(rldir); @@ -668,7 +673,7 @@ pgarch_readyXlog(char *xlog) */ arch_files->arch_files_size = arch_files->arch_heap->bh_size; for (int i = 0; i < arch_files->arch_files_size; i++) - arch_files->arch_files[i] = DatumGetCString(binaryheap_remove_first(arch_files->arch_heap)); + binaryheap_remove_first(arch_files->arch_heap, &arch_files->arch_files[i]); /* Return the highest priority file. */ arch_files->arch_files_size--; @@ -686,10 +691,10 @@ pgarch_readyXlog(char *xlog) * If "a" and "b" have equivalent values, 0 will be returned. */ static int -ready_file_comparator(Datum a, Datum b, void *arg) +ready_file_comparator(void *a, void *b, void *arg) { - char *a_str = DatumGetCString(a); - char *b_str = DatumGetCString(b); + char *a_str = *((char **) a); + char *b_str = *((char **) b); bool a_history = IsTLHistoryFileName(a_str); bool b_history = IsTLHistoryFileName(b_str); diff --git a/src/backend/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c index 12edc5772a..b0a84c5ed1 100644 --- a/src/backend/replication/logical/reorderbuffer.c +++ b/src/backend/replication/logical/reorderbuffer.c @@ -1223,11 +1223,11 @@ ReorderBufferCommitChild(ReorderBuffer *rb, TransactionId xid, * Binary heap comparison function. */ static int -ReorderBufferIterCompare(Datum a, Datum b, void *arg) +ReorderBufferIterCompare(void *a, void *b, void *arg) { ReorderBufferIterTXNState *state = (ReorderBufferIterTXNState *) arg; - XLogRecPtr pos_a = state->entries[DatumGetInt32(a)].lsn; - XLogRecPtr pos_b = state->entries[DatumGetInt32(b)].lsn; + XLogRecPtr pos_a = state->entries[*((int *) a)].lsn; + XLogRecPtr pos_b = state->entries[*((int *) b)].lsn; if (pos_a < pos_b) return 1; @@ -1296,7 +1296,7 @@ ReorderBufferIterTXNInit(ReorderBuffer *rb, ReorderBufferTXN *txn, } /* allocate heap */ - state->heap = binaryheap_allocate(state->nr_txns, + state->heap = binaryheap_allocate(state->nr_txns, sizeof(int), ReorderBufferIterCompare, state); @@ -1330,7 +1330,8 @@ ReorderBufferIterTXNInit(ReorderBuffer *rb, ReorderBufferTXN *txn, state->entries[off].change = cur_change; state->entries[off].txn = txn; - binaryheap_add_unordered(state->heap, Int32GetDatum(off++)); + binaryheap_add_unordered(state->heap, &off); + off++; } /* add subtransactions if they contain changes */ @@ -1359,7 +1360,8 @@ ReorderBufferIterTXNInit(ReorderBuffer *rb, ReorderBufferTXN *txn, state->entries[off].change = cur_change; state->entries[off].txn = cur_txn; - binaryheap_add_unordered(state->heap, Int32GetDatum(off++)); + binaryheap_add_unordered(state->heap, &off); + off++; } } @@ -1384,7 +1386,7 @@ ReorderBufferIterTXNNext(ReorderBuffer *rb, ReorderBufferIterTXNState *state) if (state->heap->bh_size == 0) return NULL; - off = DatumGetInt32(binaryheap_first(state->heap)); + binaryheap_first(state->heap, &off); entry = &state->entries[off]; /* free memory we might have "leaked" in the previous *Next call */ @@ -1414,7 +1416,7 @@ ReorderBufferIterTXNNext(ReorderBuffer *rb, ReorderBufferIterTXNState *state) state->entries[off].lsn = next_change->lsn; state->entries[off].change = next_change; - binaryheap_replace_first(state->heap, Int32GetDatum(off)); + binaryheap_replace_first(state->heap, &off); return change; } @@ -1450,14 +1452,14 @@ ReorderBufferIterTXNNext(ReorderBuffer *rb, ReorderBufferIterTXNState *state) /* txn stays the same */ state->entries[off].lsn = next_change->lsn; state->entries[off].change = next_change; - binaryheap_replace_first(state->heap, Int32GetDatum(off)); + binaryheap_replace_first(state->heap, &off); return change; } } /* ok, no changes there anymore, remove */ - binaryheap_remove_first(state->heap); + binaryheap_remove_first(state->heap, &off); return change; } diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 3bd82dbfca..5bd7176b3c 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -501,7 +501,7 @@ static void CheckForBufferLeaks(void); static int rlocator_comparator(const void *p1, const void *p2); static inline int buffertag_comparator(const BufferTag *ba, const BufferTag *bb); static inline int ckpt_buforder_comparator(const CkptSortItem *a, const CkptSortItem *b); -static int ts_ckpt_progress_comparator(Datum a, Datum b, void *arg); +static int ts_ckpt_progress_comparator(void *a, void *b, void *arg); /* @@ -2639,7 +2639,7 @@ BufferSync(int flags) * and compute how large a portion of the total progress a single * processed buffer is. */ - ts_heap = binaryheap_allocate(num_spaces, + ts_heap = binaryheap_allocate(num_spaces, sizeof(CkptTsStatus *), ts_ckpt_progress_comparator, NULL); @@ -2649,7 +2649,7 @@ BufferSync(int flags) ts_stat->progress_slice = (float8) num_to_scan / ts_stat->num_to_scan; - binaryheap_add_unordered(ts_heap, PointerGetDatum(ts_stat)); + binaryheap_add_unordered(ts_heap, &ts_stat); } binaryheap_build(ts_heap); @@ -2665,8 +2665,9 @@ BufferSync(int flags) while (!binaryheap_empty(ts_heap)) { BufferDesc *bufHdr = NULL; - CkptTsStatus *ts_stat = (CkptTsStatus *) - DatumGetPointer(binaryheap_first(ts_heap)); + CkptTsStatus *ts_stat; + + binaryheap_first(ts_heap, &ts_stat); buf_id = CkptBufferIds[ts_stat->index].buf_id; Assert(buf_id != -1); @@ -2708,12 +2709,12 @@ BufferSync(int flags) /* Have all the buffers from the tablespace been processed? */ if (ts_stat->num_scanned == ts_stat->num_to_scan) { - binaryheap_remove_first(ts_heap); + binaryheap_remove_first(ts_heap, &ts_stat); } else { /* update heap with the new progress */ - binaryheap_replace_first(ts_heap, PointerGetDatum(ts_stat)); + binaryheap_replace_first(ts_heap, &ts_stat); } /* @@ -5416,10 +5417,10 @@ ckpt_buforder_comparator(const CkptSortItem *a, const CkptSortItem *b) * progress. */ static int -ts_ckpt_progress_comparator(Datum a, Datum b, void *arg) +ts_ckpt_progress_comparator(void *a, void *b, void *arg) { - CkptTsStatus *sa = (CkptTsStatus *) a; - CkptTsStatus *sb = (CkptTsStatus *) b; + CkptTsStatus *sa = *((CkptTsStatus **) a); + CkptTsStatus *sb = *((CkptTsStatus **) b); /* we want a min-heap, so return 1 for the a < b */ if (sa->progress < sb->progress) diff --git a/src/include/lib/binaryheap.h b/src/include/lib/binaryheap.h index 52f7b06b25..24228e5b4b 100644 --- a/src/include/lib/binaryheap.h +++ b/src/include/lib/binaryheap.h @@ -15,7 +15,7 @@ * For a max-heap, the comparator must return <0 iff a < b, 0 iff a == b, * and >0 iff a > b. For a min-heap, the conditions are reversed. */ -typedef int (*binaryheap_comparator) (Datum a, Datum b, void *arg); +typedef int (*binaryheap_comparator) (void *a, void *b, void *arg); /* * binaryheap @@ -31,23 +31,24 @@ typedef struct binaryheap { int bh_size; int bh_space; + size_t bh_elem_size; bool bh_has_heap_property; /* debugging cross-check */ binaryheap_comparator bh_compare; void *bh_arg; - Datum bh_nodes[FLEXIBLE_ARRAY_MEMBER]; + char bh_nodes[FLEXIBLE_ARRAY_MEMBER]; } binaryheap; -extern binaryheap *binaryheap_allocate(int capacity, +extern binaryheap *binaryheap_allocate(int capacity, size_t elem_size, binaryheap_comparator compare, void *arg); extern void binaryheap_reset(binaryheap *heap); extern void binaryheap_free(binaryheap *heap); -extern void binaryheap_add_unordered(binaryheap *heap, Datum d); +extern void binaryheap_add_unordered(binaryheap *heap, void *d); extern void binaryheap_build(binaryheap *heap); -extern void binaryheap_add(binaryheap *heap, Datum d); -extern Datum binaryheap_first(binaryheap *heap); -extern Datum binaryheap_remove_first(binaryheap *heap); -extern void binaryheap_replace_first(binaryheap *heap, Datum d); +extern void binaryheap_add(binaryheap *heap, void *d); +extern void binaryheap_first(binaryheap *heap, void *result); +extern void binaryheap_remove_first(binaryheap *heap, void *result); +extern void binaryheap_replace_first(binaryheap *heap, void *d); #define binaryheap_empty(h) ((h)->bh_size == 0) -- 2.25.1 --fUYQa+Pmc3FrFX/N-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v7 1/5] Allow binaryheap to use any type. @ 2023-09-04 22:04 Nathan Bossart <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nathan Bossart @ 2023-09-04 22:04 UTC (permalink / raw) We would like to make binaryheap available to frontend code in a future commit, but presently the implementation uses Datum for the node type, which is inaccessible in the frontend. This commit modifies the implementation to allow using any type for the nodes. binaryheap_allocate() now requires callers to specify the size of the node, and several of the other functions now use void pointers. --- src/backend/executor/nodeGatherMerge.c | 19 ++-- src/backend/executor/nodeMergeAppend.c | 22 ++--- src/backend/lib/binaryheap.c | 99 +++++++++++-------- src/backend/postmaster/pgarch.c | 36 ++++--- .../replication/logical/reorderbuffer.c | 21 ++-- src/backend/storage/buffer/bufmgr.c | 20 ++-- src/include/lib/binaryheap.h | 21 ++-- 7 files changed, 137 insertions(+), 101 deletions(-) diff --git a/src/backend/executor/nodeGatherMerge.c b/src/backend/executor/nodeGatherMerge.c index 9d5e1a46e9..f76406b575 100644 --- a/src/backend/executor/nodeGatherMerge.c +++ b/src/backend/executor/nodeGatherMerge.c @@ -52,7 +52,7 @@ typedef struct GMReaderTupleBuffer } GMReaderTupleBuffer; static TupleTableSlot *ExecGatherMerge(PlanState *pstate); -static int32 heap_compare_slots(Datum a, Datum b, void *arg); +static int32 heap_compare_slots(const void *a, const void *b, void *arg); static TupleTableSlot *gather_merge_getnext(GatherMergeState *gm_state); static MinimalTuple gm_readnext_tuple(GatherMergeState *gm_state, int nreader, bool nowait, bool *done); @@ -429,6 +429,7 @@ gather_merge_setup(GatherMergeState *gm_state) /* Allocate the resources for the merge */ gm_state->gm_heap = binaryheap_allocate(nreaders + 1, + sizeof(int), heap_compare_slots, gm_state); } @@ -489,7 +490,7 @@ reread: /* Don't have a tuple yet, try to get one */ if (gather_merge_readnext(gm_state, i, nowait)) binaryheap_add_unordered(gm_state->gm_heap, - Int32GetDatum(i)); + &i); } else { @@ -565,14 +566,14 @@ gather_merge_getnext(GatherMergeState *gm_state) * the heap, because it might now compare differently against the * other elements of the heap. */ - i = DatumGetInt32(binaryheap_first(gm_state->gm_heap)); + binaryheap_first(gm_state->gm_heap, &i); if (gather_merge_readnext(gm_state, i, false)) - binaryheap_replace_first(gm_state->gm_heap, Int32GetDatum(i)); + binaryheap_replace_first(gm_state->gm_heap, &i); else { /* reader exhausted, remove it from heap */ - (void) binaryheap_remove_first(gm_state->gm_heap); + binaryheap_remove_first(gm_state->gm_heap, NULL); } } @@ -585,7 +586,7 @@ gather_merge_getnext(GatherMergeState *gm_state) else { /* Return next tuple from whichever participant has the leading one */ - i = DatumGetInt32(binaryheap_first(gm_state->gm_heap)); + binaryheap_first(gm_state->gm_heap, &i); return gm_state->gm_slots[i]; } } @@ -750,11 +751,11 @@ typedef int32 SlotNumber; * Compare the tuples in the two given slots. */ static int32 -heap_compare_slots(Datum a, Datum b, void *arg) +heap_compare_slots(const void *a, const void *b, void *arg) { GatherMergeState *node = (GatherMergeState *) arg; - SlotNumber slot1 = DatumGetInt32(a); - SlotNumber slot2 = DatumGetInt32(b); + SlotNumber slot1 = *((const int *) a); + SlotNumber slot2 = *((const int *) b); TupleTableSlot *s1 = node->gm_slots[slot1]; TupleTableSlot *s2 = node->gm_slots[slot2]; diff --git a/src/backend/executor/nodeMergeAppend.c b/src/backend/executor/nodeMergeAppend.c index 21b5726e6e..e0ace294a0 100644 --- a/src/backend/executor/nodeMergeAppend.c +++ b/src/backend/executor/nodeMergeAppend.c @@ -52,7 +52,7 @@ typedef int32 SlotNumber; static TupleTableSlot *ExecMergeAppend(PlanState *pstate); -static int heap_compare_slots(Datum a, Datum b, void *arg); +static int heap_compare_slots(const void *a, const void *b, void *arg); /* ---------------------------------------------------------------- @@ -125,8 +125,8 @@ ExecInitMergeAppend(MergeAppend *node, EState *estate, int eflags) mergestate->ms_nplans = nplans; mergestate->ms_slots = (TupleTableSlot **) palloc0(sizeof(TupleTableSlot *) * nplans); - mergestate->ms_heap = binaryheap_allocate(nplans, heap_compare_slots, - mergestate); + mergestate->ms_heap = binaryheap_allocate(nplans, sizeof(int), + heap_compare_slots, mergestate); /* * Miscellaneous initialization @@ -229,7 +229,7 @@ ExecMergeAppend(PlanState *pstate) { node->ms_slots[i] = ExecProcNode(node->mergeplans[i]); if (!TupIsNull(node->ms_slots[i])) - binaryheap_add_unordered(node->ms_heap, Int32GetDatum(i)); + binaryheap_add_unordered(node->ms_heap, &i); } binaryheap_build(node->ms_heap); node->ms_initialized = true; @@ -244,12 +244,12 @@ ExecMergeAppend(PlanState *pstate) * by doing this before returning from the prior call, but it's better * to not pull tuples until necessary.) */ - i = DatumGetInt32(binaryheap_first(node->ms_heap)); + binaryheap_first(node->ms_heap, &i); node->ms_slots[i] = ExecProcNode(node->mergeplans[i]); if (!TupIsNull(node->ms_slots[i])) - binaryheap_replace_first(node->ms_heap, Int32GetDatum(i)); + binaryheap_replace_first(node->ms_heap, &i); else - (void) binaryheap_remove_first(node->ms_heap); + binaryheap_remove_first(node->ms_heap, NULL); } if (binaryheap_empty(node->ms_heap)) @@ -259,7 +259,7 @@ ExecMergeAppend(PlanState *pstate) } else { - i = DatumGetInt32(binaryheap_first(node->ms_heap)); + binaryheap_first(node->ms_heap, &i); result = node->ms_slots[i]; } @@ -270,11 +270,11 @@ ExecMergeAppend(PlanState *pstate) * Compare the tuples in the two given slots. */ static int32 -heap_compare_slots(Datum a, Datum b, void *arg) +heap_compare_slots(const void *a, const void *b, void *arg) { MergeAppendState *node = (MergeAppendState *) arg; - SlotNumber slot1 = DatumGetInt32(a); - SlotNumber slot2 = DatumGetInt32(b); + SlotNumber slot1 = *((const int *) a); + SlotNumber slot2 = *((const int *) b); TupleTableSlot *s1 = node->ms_slots[slot1]; TupleTableSlot *s2 = node->ms_slots[slot2]; diff --git a/src/backend/lib/binaryheap.c b/src/backend/lib/binaryheap.c index 1737546757..6f63181c4c 100644 --- a/src/backend/lib/binaryheap.c +++ b/src/backend/lib/binaryheap.c @@ -29,16 +29,19 @@ static void sift_up(binaryheap *heap, int node_off); * argument specified by 'arg'. */ binaryheap * -binaryheap_allocate(int capacity, binaryheap_comparator compare, void *arg) +binaryheap_allocate(int capacity, size_t elem_size, + binaryheap_comparator compare, void *arg) { int sz; binaryheap *heap; - sz = offsetof(binaryheap, bh_nodes) + sizeof(Datum) * capacity; + sz = offsetof(binaryheap, bh_nodes) + elem_size * (capacity + 1); heap = (binaryheap *) palloc(sz); heap->bh_space = capacity; + heap->bh_elem_size = elem_size; heap->bh_compare = compare; heap->bh_arg = arg; + heap->bh_hole = &heap->bh_nodes[capacity * elem_size]; heap->bh_size = 0; heap->bh_has_heap_property = true; @@ -97,6 +100,27 @@ parent_offset(int i) return (i - 1) / 2; } +/* + * This utility function returns a pointer to the nth node of the binary heap. + */ +static inline void * +bh_node(binaryheap *heap, int n) +{ + return &heap->bh_nodes[n * heap->bh_elem_size]; +} + +/* + * This utility function sets the nth node of the binary heap to the value that + * d points to. + */ +static inline void +bh_set(binaryheap *heap, int n, const void *d) +{ + void *node = bh_node(heap, n); + + memmove(node, d, heap->bh_elem_size); +} + /* * binaryheap_add_unordered * @@ -106,12 +130,12 @@ parent_offset(int i) * afterwards. */ void -binaryheap_add_unordered(binaryheap *heap, Datum d) +binaryheap_add_unordered(binaryheap *heap, void *d) { if (heap->bh_size >= heap->bh_space) elog(ERROR, "out of binary heap slots"); heap->bh_has_heap_property = false; - heap->bh_nodes[heap->bh_size] = d; + bh_set(heap, heap->bh_size, d); heap->bh_size++; } @@ -138,11 +162,11 @@ binaryheap_build(binaryheap *heap) * the heap property. */ void -binaryheap_add(binaryheap *heap, Datum d) +binaryheap_add(binaryheap *heap, void *d) { if (heap->bh_size >= heap->bh_space) elog(ERROR, "out of binary heap slots"); - heap->bh_nodes[heap->bh_size] = d; + bh_set(heap, heap->bh_size, d); heap->bh_size++; sift_up(heap, heap->bh_size - 1); } @@ -154,11 +178,11 @@ binaryheap_add(binaryheap *heap, Datum d) * without modifying the heap. The caller must ensure that this * routine is not used on an empty heap. Always O(1). */ -Datum -binaryheap_first(binaryheap *heap) +void +binaryheap_first(binaryheap *heap, void *result) { Assert(!binaryheap_empty(heap) && heap->bh_has_heap_property); - return heap->bh_nodes[0]; + memmove(result, heap->bh_nodes, heap->bh_elem_size); } /* @@ -169,31 +193,28 @@ binaryheap_first(binaryheap *heap) * that this routine is not used on an empty heap. O(log n) worst * case. */ -Datum -binaryheap_remove_first(binaryheap *heap) +void +binaryheap_remove_first(binaryheap *heap, void *result) { - Datum result; - Assert(!binaryheap_empty(heap) && heap->bh_has_heap_property); /* extract the root node, which will be the result */ - result = heap->bh_nodes[0]; + if (result) + memmove(result, heap->bh_nodes, heap->bh_elem_size); /* easy if heap contains one element */ if (heap->bh_size == 1) { heap->bh_size--; - return result; + return; } /* * Remove the last node, placing it in the vacated root entry, and sift * the new root node down to its correct position. */ - heap->bh_nodes[0] = heap->bh_nodes[--heap->bh_size]; + bh_set(heap, 0, bh_node(heap, --heap->bh_size)); sift_down(heap, 0); - - return result; } /* @@ -204,11 +225,11 @@ binaryheap_remove_first(binaryheap *heap) * sifting the new node down. */ void -binaryheap_replace_first(binaryheap *heap, Datum d) +binaryheap_replace_first(binaryheap *heap, void *d) { Assert(!binaryheap_empty(heap) && heap->bh_has_heap_property); - heap->bh_nodes[0] = d; + bh_set(heap, 0, d); if (heap->bh_size > 1) sift_down(heap, 0); @@ -221,26 +242,26 @@ binaryheap_replace_first(binaryheap *heap, Datum d) static void sift_up(binaryheap *heap, int node_off) { - Datum node_val = heap->bh_nodes[node_off]; + memmove(heap->bh_hole, bh_node(heap, node_off), heap->bh_elem_size); /* * Within the loop, the node_off'th array entry is a "hole" that - * notionally holds node_val, but we don't actually store node_val there - * till the end, saving some unnecessary data copying steps. + * notionally holds the swapped value, but we don't actually store the + * value there till the end, saving some unnecessary data copying steps. */ while (node_off != 0) { int cmp; int parent_off; - Datum parent_val; + void *parent_val; /* * If this node is smaller than its parent, the heap condition is * satisfied, and we're done. */ parent_off = parent_offset(node_off); - parent_val = heap->bh_nodes[parent_off]; - cmp = heap->bh_compare(node_val, + parent_val = bh_node(heap, parent_off); + cmp = heap->bh_compare(heap->bh_hole, parent_val, heap->bh_arg); if (cmp <= 0) @@ -250,11 +271,11 @@ sift_up(binaryheap *heap, int node_off) * Otherwise, swap the parent value with the hole, and go on to check * the node's new parent. */ - heap->bh_nodes[node_off] = parent_val; + bh_set(heap, node_off, parent_val); node_off = parent_off; } /* Re-fill the hole */ - heap->bh_nodes[node_off] = node_val; + bh_set(heap, node_off, heap->bh_hole); } /* @@ -264,12 +285,12 @@ sift_up(binaryheap *heap, int node_off) static void sift_down(binaryheap *heap, int node_off) { - Datum node_val = heap->bh_nodes[node_off]; + memmove(heap->bh_hole, bh_node(heap, node_off), heap->bh_elem_size); /* * Within the loop, the node_off'th array entry is a "hole" that - * notionally holds node_val, but we don't actually store node_val there - * till the end, saving some unnecessary data copying steps. + * notionally holds the swapped value, but we don't actually store the + * value there till the end, saving some unnecessary data copying steps. */ while (true) { @@ -279,21 +300,21 @@ sift_down(binaryheap *heap, int node_off) /* Is the left child larger than the parent? */ if (left_off < heap->bh_size && - heap->bh_compare(node_val, - heap->bh_nodes[left_off], + heap->bh_compare(heap->bh_hole, + bh_node(heap, left_off), heap->bh_arg) < 0) swap_off = left_off; /* Is the right child larger than the parent? */ if (right_off < heap->bh_size && - heap->bh_compare(node_val, - heap->bh_nodes[right_off], + heap->bh_compare(heap->bh_hole, + bh_node(heap, right_off), heap->bh_arg) < 0) { /* swap with the larger child */ if (!swap_off || - heap->bh_compare(heap->bh_nodes[left_off], - heap->bh_nodes[right_off], + heap->bh_compare(bh_node(heap, left_off), + bh_node(heap, right_off), heap->bh_arg) < 0) swap_off = right_off; } @@ -309,9 +330,9 @@ sift_down(binaryheap *heap, int node_off) * Otherwise, swap the hole with the child that violates the heap * property; then go on to check its children. */ - heap->bh_nodes[node_off] = heap->bh_nodes[swap_off]; + bh_set(heap, node_off, bh_node(heap, swap_off)); node_off = swap_off; } /* Re-fill the hole */ - heap->bh_nodes[node_off] = node_val; + bh_set(heap, node_off, heap->bh_hole); } diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c index 46af349564..f7ee95d8f9 100644 --- a/src/backend/postmaster/pgarch.c +++ b/src/backend/postmaster/pgarch.c @@ -145,7 +145,7 @@ static bool pgarch_readyXlog(char *xlog); static void pgarch_archiveDone(char *xlog); static void pgarch_die(int code, Datum arg); static void HandlePgArchInterrupts(void); -static int ready_file_comparator(Datum a, Datum b, void *arg); +static int ready_file_comparator(const void *a, const void *b, void *arg); static void LoadArchiveLibrary(void); static void pgarch_call_module_shutdown_cb(int code, Datum arg); @@ -250,6 +250,7 @@ PgArchiverMain(void) /* Initialize our max-heap for prioritizing files to archive. */ arch_files->arch_heap = binaryheap_allocate(NUM_FILES_PER_DIRECTORY_SCAN, + sizeof(char *), ready_file_comparator, NULL); /* Load the archive_library. */ @@ -631,22 +632,27 @@ pgarch_readyXlog(char *xlog) /* If the heap isn't full yet, quickly add it. */ arch_file = arch_files->arch_filenames[arch_files->arch_heap->bh_size]; strcpy(arch_file, basename); - binaryheap_add_unordered(arch_files->arch_heap, CStringGetDatum(arch_file)); + binaryheap_add_unordered(arch_files->arch_heap, &arch_file); /* If we just filled the heap, make it a valid one. */ if (arch_files->arch_heap->bh_size == NUM_FILES_PER_DIRECTORY_SCAN) binaryheap_build(arch_files->arch_heap); } - else if (ready_file_comparator(binaryheap_first(arch_files->arch_heap), - CStringGetDatum(basename), NULL) > 0) + else { - /* - * Remove the lowest priority file and add the current one to the - * heap. - */ - arch_file = DatumGetCString(binaryheap_remove_first(arch_files->arch_heap)); - strcpy(arch_file, basename); - binaryheap_add(arch_files->arch_heap, CStringGetDatum(arch_file)); + char *root; + + binaryheap_first(arch_files->arch_heap, &root); + if (ready_file_comparator(&root, &basename, NULL) > 0) + { + /* + * Remove the lowest priority file and add the current one to + * the heap. + */ + binaryheap_remove_first(arch_files->arch_heap, &arch_file); + strcpy(arch_file, basename); + binaryheap_add(arch_files->arch_heap, &arch_file); + } } } FreeDir(rldir); @@ -668,7 +674,7 @@ pgarch_readyXlog(char *xlog) */ arch_files->arch_files_size = arch_files->arch_heap->bh_size; for (int i = 0; i < arch_files->arch_files_size; i++) - arch_files->arch_files[i] = DatumGetCString(binaryheap_remove_first(arch_files->arch_heap)); + binaryheap_remove_first(arch_files->arch_heap, &arch_files->arch_files[i]); /* Return the highest priority file. */ arch_files->arch_files_size--; @@ -686,10 +692,10 @@ pgarch_readyXlog(char *xlog) * If "a" and "b" have equivalent values, 0 will be returned. */ static int -ready_file_comparator(Datum a, Datum b, void *arg) +ready_file_comparator(const void *a, const void *b, void *arg) { - char *a_str = DatumGetCString(a); - char *b_str = DatumGetCString(b); + const char *a_str = *((const char **) a); + const char *b_str = *((const char **) b); bool a_history = IsTLHistoryFileName(a_str); bool b_history = IsTLHistoryFileName(b_str); diff --git a/src/backend/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c index 12edc5772a..e22680da0b 100644 --- a/src/backend/replication/logical/reorderbuffer.c +++ b/src/backend/replication/logical/reorderbuffer.c @@ -1223,11 +1223,11 @@ ReorderBufferCommitChild(ReorderBuffer *rb, TransactionId xid, * Binary heap comparison function. */ static int -ReorderBufferIterCompare(Datum a, Datum b, void *arg) +ReorderBufferIterCompare(const void *a, const void *b, void *arg) { ReorderBufferIterTXNState *state = (ReorderBufferIterTXNState *) arg; - XLogRecPtr pos_a = state->entries[DatumGetInt32(a)].lsn; - XLogRecPtr pos_b = state->entries[DatumGetInt32(b)].lsn; + XLogRecPtr pos_a = state->entries[*((const int *) a)].lsn; + XLogRecPtr pos_b = state->entries[*((const int *) b)].lsn; if (pos_a < pos_b) return 1; @@ -1297,6 +1297,7 @@ ReorderBufferIterTXNInit(ReorderBuffer *rb, ReorderBufferTXN *txn, /* allocate heap */ state->heap = binaryheap_allocate(state->nr_txns, + sizeof(int), ReorderBufferIterCompare, state); @@ -1330,7 +1331,8 @@ ReorderBufferIterTXNInit(ReorderBuffer *rb, ReorderBufferTXN *txn, state->entries[off].change = cur_change; state->entries[off].txn = txn; - binaryheap_add_unordered(state->heap, Int32GetDatum(off++)); + binaryheap_add_unordered(state->heap, &off); + off++; } /* add subtransactions if they contain changes */ @@ -1359,7 +1361,8 @@ ReorderBufferIterTXNInit(ReorderBuffer *rb, ReorderBufferTXN *txn, state->entries[off].change = cur_change; state->entries[off].txn = cur_txn; - binaryheap_add_unordered(state->heap, Int32GetDatum(off++)); + binaryheap_add_unordered(state->heap, &off); + off++; } } @@ -1384,7 +1387,7 @@ ReorderBufferIterTXNNext(ReorderBuffer *rb, ReorderBufferIterTXNState *state) if (state->heap->bh_size == 0) return NULL; - off = DatumGetInt32(binaryheap_first(state->heap)); + binaryheap_first(state->heap, &off); entry = &state->entries[off]; /* free memory we might have "leaked" in the previous *Next call */ @@ -1414,7 +1417,7 @@ ReorderBufferIterTXNNext(ReorderBuffer *rb, ReorderBufferIterTXNState *state) state->entries[off].lsn = next_change->lsn; state->entries[off].change = next_change; - binaryheap_replace_first(state->heap, Int32GetDatum(off)); + binaryheap_replace_first(state->heap, &off); return change; } @@ -1450,14 +1453,14 @@ ReorderBufferIterTXNNext(ReorderBuffer *rb, ReorderBufferIterTXNState *state) /* txn stays the same */ state->entries[off].lsn = next_change->lsn; state->entries[off].change = next_change; - binaryheap_replace_first(state->heap, Int32GetDatum(off)); + binaryheap_replace_first(state->heap, &off); return change; } } /* ok, no changes there anymore, remove */ - binaryheap_remove_first(state->heap); + binaryheap_remove_first(state->heap, NULL); return change; } diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 3bd82dbfca..7c78a0d625 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -501,7 +501,7 @@ static void CheckForBufferLeaks(void); static int rlocator_comparator(const void *p1, const void *p2); static inline int buffertag_comparator(const BufferTag *ba, const BufferTag *bb); static inline int ckpt_buforder_comparator(const CkptSortItem *a, const CkptSortItem *b); -static int ts_ckpt_progress_comparator(Datum a, Datum b, void *arg); +static int ts_ckpt_progress_comparator(const void *a, const void *b, void *arg); /* @@ -2640,6 +2640,7 @@ BufferSync(int flags) * processed buffer is. */ ts_heap = binaryheap_allocate(num_spaces, + sizeof(CkptTsStatus *), ts_ckpt_progress_comparator, NULL); @@ -2649,7 +2650,7 @@ BufferSync(int flags) ts_stat->progress_slice = (float8) num_to_scan / ts_stat->num_to_scan; - binaryheap_add_unordered(ts_heap, PointerGetDatum(ts_stat)); + binaryheap_add_unordered(ts_heap, &ts_stat); } binaryheap_build(ts_heap); @@ -2665,8 +2666,9 @@ BufferSync(int flags) while (!binaryheap_empty(ts_heap)) { BufferDesc *bufHdr = NULL; - CkptTsStatus *ts_stat = (CkptTsStatus *) - DatumGetPointer(binaryheap_first(ts_heap)); + CkptTsStatus *ts_stat; + + binaryheap_first(ts_heap, &ts_stat); buf_id = CkptBufferIds[ts_stat->index].buf_id; Assert(buf_id != -1); @@ -2708,12 +2710,12 @@ BufferSync(int flags) /* Have all the buffers from the tablespace been processed? */ if (ts_stat->num_scanned == ts_stat->num_to_scan) { - binaryheap_remove_first(ts_heap); + binaryheap_remove_first(ts_heap, NULL); } else { /* update heap with the new progress */ - binaryheap_replace_first(ts_heap, PointerGetDatum(ts_stat)); + binaryheap_replace_first(ts_heap, &ts_stat); } /* @@ -5416,10 +5418,10 @@ ckpt_buforder_comparator(const CkptSortItem *a, const CkptSortItem *b) * progress. */ static int -ts_ckpt_progress_comparator(Datum a, Datum b, void *arg) +ts_ckpt_progress_comparator(const void *a, const void *b, void *arg) { - CkptTsStatus *sa = (CkptTsStatus *) a; - CkptTsStatus *sb = (CkptTsStatus *) b; + const CkptTsStatus *sa = *((const CkptTsStatus **) a); + const CkptTsStatus *sb = *((const CkptTsStatus **) b); /* we want a min-heap, so return 1 for the a < b */ if (sa->progress < sb->progress) diff --git a/src/include/lib/binaryheap.h b/src/include/lib/binaryheap.h index 52f7b06b25..b8c7ef81ef 100644 --- a/src/include/lib/binaryheap.h +++ b/src/include/lib/binaryheap.h @@ -15,7 +15,7 @@ * For a max-heap, the comparator must return <0 iff a < b, 0 iff a == b, * and >0 iff a > b. For a min-heap, the conditions are reversed. */ -typedef int (*binaryheap_comparator) (Datum a, Datum b, void *arg); +typedef int (*binaryheap_comparator) (const void *a, const void *b, void *arg); /* * binaryheap @@ -25,29 +25,32 @@ typedef int (*binaryheap_comparator) (Datum a, Datum b, void *arg); * bh_has_heap_property no unordered operations since last heap build * bh_compare comparison function to define the heap property * bh_arg user data for comparison function - * bh_nodes variable-length array of "space" nodes + * bh_hole extra node used during sifting operations + * bh_nodes variable-length array of "space + 1" nodes */ typedef struct binaryheap { int bh_size; int bh_space; + size_t bh_elem_size; bool bh_has_heap_property; /* debugging cross-check */ binaryheap_comparator bh_compare; void *bh_arg; - Datum bh_nodes[FLEXIBLE_ARRAY_MEMBER]; + void *bh_hole; + char bh_nodes[FLEXIBLE_ARRAY_MEMBER]; } binaryheap; -extern binaryheap *binaryheap_allocate(int capacity, +extern binaryheap *binaryheap_allocate(int capacity, size_t elem_size, binaryheap_comparator compare, void *arg); extern void binaryheap_reset(binaryheap *heap); extern void binaryheap_free(binaryheap *heap); -extern void binaryheap_add_unordered(binaryheap *heap, Datum d); +extern void binaryheap_add_unordered(binaryheap *heap, void *d); extern void binaryheap_build(binaryheap *heap); -extern void binaryheap_add(binaryheap *heap, Datum d); -extern Datum binaryheap_first(binaryheap *heap); -extern Datum binaryheap_remove_first(binaryheap *heap); -extern void binaryheap_replace_first(binaryheap *heap, Datum d); +extern void binaryheap_add(binaryheap *heap, void *d); +extern void binaryheap_first(binaryheap *heap, void *result); +extern void binaryheap_remove_first(binaryheap *heap, void *result); +extern void binaryheap_replace_first(binaryheap *heap, void *d); #define binaryheap_empty(h) ((h)->bh_size == 0) -- 2.25.1 --DocE+STaALJfprDB Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v7-0002-Make-binaryheap-available-to-frontend-code.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v7 1/5] Allow binaryheap to use any type. @ 2023-09-04 22:04 Nathan Bossart <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nathan Bossart @ 2023-09-04 22:04 UTC (permalink / raw) We would like to make binaryheap available to frontend code in a future commit, but presently the implementation uses Datum for the node type, which is inaccessible in the frontend. This commit modifies the implementation to allow using any type for the nodes. binaryheap_allocate() now requires callers to specify the size of the node, and several of the other functions now use void pointers. --- src/backend/executor/nodeGatherMerge.c | 19 ++-- src/backend/executor/nodeMergeAppend.c | 22 ++--- src/backend/lib/binaryheap.c | 99 +++++++++++-------- src/backend/postmaster/pgarch.c | 36 ++++--- .../replication/logical/reorderbuffer.c | 21 ++-- src/backend/storage/buffer/bufmgr.c | 20 ++-- src/include/lib/binaryheap.h | 21 ++-- 7 files changed, 137 insertions(+), 101 deletions(-) diff --git a/src/backend/executor/nodeGatherMerge.c b/src/backend/executor/nodeGatherMerge.c index 9d5e1a46e9..f76406b575 100644 --- a/src/backend/executor/nodeGatherMerge.c +++ b/src/backend/executor/nodeGatherMerge.c @@ -52,7 +52,7 @@ typedef struct GMReaderTupleBuffer } GMReaderTupleBuffer; static TupleTableSlot *ExecGatherMerge(PlanState *pstate); -static int32 heap_compare_slots(Datum a, Datum b, void *arg); +static int32 heap_compare_slots(const void *a, const void *b, void *arg); static TupleTableSlot *gather_merge_getnext(GatherMergeState *gm_state); static MinimalTuple gm_readnext_tuple(GatherMergeState *gm_state, int nreader, bool nowait, bool *done); @@ -429,6 +429,7 @@ gather_merge_setup(GatherMergeState *gm_state) /* Allocate the resources for the merge */ gm_state->gm_heap = binaryheap_allocate(nreaders + 1, + sizeof(int), heap_compare_slots, gm_state); } @@ -489,7 +490,7 @@ reread: /* Don't have a tuple yet, try to get one */ if (gather_merge_readnext(gm_state, i, nowait)) binaryheap_add_unordered(gm_state->gm_heap, - Int32GetDatum(i)); + &i); } else { @@ -565,14 +566,14 @@ gather_merge_getnext(GatherMergeState *gm_state) * the heap, because it might now compare differently against the * other elements of the heap. */ - i = DatumGetInt32(binaryheap_first(gm_state->gm_heap)); + binaryheap_first(gm_state->gm_heap, &i); if (gather_merge_readnext(gm_state, i, false)) - binaryheap_replace_first(gm_state->gm_heap, Int32GetDatum(i)); + binaryheap_replace_first(gm_state->gm_heap, &i); else { /* reader exhausted, remove it from heap */ - (void) binaryheap_remove_first(gm_state->gm_heap); + binaryheap_remove_first(gm_state->gm_heap, NULL); } } @@ -585,7 +586,7 @@ gather_merge_getnext(GatherMergeState *gm_state) else { /* Return next tuple from whichever participant has the leading one */ - i = DatumGetInt32(binaryheap_first(gm_state->gm_heap)); + binaryheap_first(gm_state->gm_heap, &i); return gm_state->gm_slots[i]; } } @@ -750,11 +751,11 @@ typedef int32 SlotNumber; * Compare the tuples in the two given slots. */ static int32 -heap_compare_slots(Datum a, Datum b, void *arg) +heap_compare_slots(const void *a, const void *b, void *arg) { GatherMergeState *node = (GatherMergeState *) arg; - SlotNumber slot1 = DatumGetInt32(a); - SlotNumber slot2 = DatumGetInt32(b); + SlotNumber slot1 = *((const int *) a); + SlotNumber slot2 = *((const int *) b); TupleTableSlot *s1 = node->gm_slots[slot1]; TupleTableSlot *s2 = node->gm_slots[slot2]; diff --git a/src/backend/executor/nodeMergeAppend.c b/src/backend/executor/nodeMergeAppend.c index 21b5726e6e..e0ace294a0 100644 --- a/src/backend/executor/nodeMergeAppend.c +++ b/src/backend/executor/nodeMergeAppend.c @@ -52,7 +52,7 @@ typedef int32 SlotNumber; static TupleTableSlot *ExecMergeAppend(PlanState *pstate); -static int heap_compare_slots(Datum a, Datum b, void *arg); +static int heap_compare_slots(const void *a, const void *b, void *arg); /* ---------------------------------------------------------------- @@ -125,8 +125,8 @@ ExecInitMergeAppend(MergeAppend *node, EState *estate, int eflags) mergestate->ms_nplans = nplans; mergestate->ms_slots = (TupleTableSlot **) palloc0(sizeof(TupleTableSlot *) * nplans); - mergestate->ms_heap = binaryheap_allocate(nplans, heap_compare_slots, - mergestate); + mergestate->ms_heap = binaryheap_allocate(nplans, sizeof(int), + heap_compare_slots, mergestate); /* * Miscellaneous initialization @@ -229,7 +229,7 @@ ExecMergeAppend(PlanState *pstate) { node->ms_slots[i] = ExecProcNode(node->mergeplans[i]); if (!TupIsNull(node->ms_slots[i])) - binaryheap_add_unordered(node->ms_heap, Int32GetDatum(i)); + binaryheap_add_unordered(node->ms_heap, &i); } binaryheap_build(node->ms_heap); node->ms_initialized = true; @@ -244,12 +244,12 @@ ExecMergeAppend(PlanState *pstate) * by doing this before returning from the prior call, but it's better * to not pull tuples until necessary.) */ - i = DatumGetInt32(binaryheap_first(node->ms_heap)); + binaryheap_first(node->ms_heap, &i); node->ms_slots[i] = ExecProcNode(node->mergeplans[i]); if (!TupIsNull(node->ms_slots[i])) - binaryheap_replace_first(node->ms_heap, Int32GetDatum(i)); + binaryheap_replace_first(node->ms_heap, &i); else - (void) binaryheap_remove_first(node->ms_heap); + binaryheap_remove_first(node->ms_heap, NULL); } if (binaryheap_empty(node->ms_heap)) @@ -259,7 +259,7 @@ ExecMergeAppend(PlanState *pstate) } else { - i = DatumGetInt32(binaryheap_first(node->ms_heap)); + binaryheap_first(node->ms_heap, &i); result = node->ms_slots[i]; } @@ -270,11 +270,11 @@ ExecMergeAppend(PlanState *pstate) * Compare the tuples in the two given slots. */ static int32 -heap_compare_slots(Datum a, Datum b, void *arg) +heap_compare_slots(const void *a, const void *b, void *arg) { MergeAppendState *node = (MergeAppendState *) arg; - SlotNumber slot1 = DatumGetInt32(a); - SlotNumber slot2 = DatumGetInt32(b); + SlotNumber slot1 = *((const int *) a); + SlotNumber slot2 = *((const int *) b); TupleTableSlot *s1 = node->ms_slots[slot1]; TupleTableSlot *s2 = node->ms_slots[slot2]; diff --git a/src/backend/lib/binaryheap.c b/src/backend/lib/binaryheap.c index 1737546757..6f63181c4c 100644 --- a/src/backend/lib/binaryheap.c +++ b/src/backend/lib/binaryheap.c @@ -29,16 +29,19 @@ static void sift_up(binaryheap *heap, int node_off); * argument specified by 'arg'. */ binaryheap * -binaryheap_allocate(int capacity, binaryheap_comparator compare, void *arg) +binaryheap_allocate(int capacity, size_t elem_size, + binaryheap_comparator compare, void *arg) { int sz; binaryheap *heap; - sz = offsetof(binaryheap, bh_nodes) + sizeof(Datum) * capacity; + sz = offsetof(binaryheap, bh_nodes) + elem_size * (capacity + 1); heap = (binaryheap *) palloc(sz); heap->bh_space = capacity; + heap->bh_elem_size = elem_size; heap->bh_compare = compare; heap->bh_arg = arg; + heap->bh_hole = &heap->bh_nodes[capacity * elem_size]; heap->bh_size = 0; heap->bh_has_heap_property = true; @@ -97,6 +100,27 @@ parent_offset(int i) return (i - 1) / 2; } +/* + * This utility function returns a pointer to the nth node of the binary heap. + */ +static inline void * +bh_node(binaryheap *heap, int n) +{ + return &heap->bh_nodes[n * heap->bh_elem_size]; +} + +/* + * This utility function sets the nth node of the binary heap to the value that + * d points to. + */ +static inline void +bh_set(binaryheap *heap, int n, const void *d) +{ + void *node = bh_node(heap, n); + + memmove(node, d, heap->bh_elem_size); +} + /* * binaryheap_add_unordered * @@ -106,12 +130,12 @@ parent_offset(int i) * afterwards. */ void -binaryheap_add_unordered(binaryheap *heap, Datum d) +binaryheap_add_unordered(binaryheap *heap, void *d) { if (heap->bh_size >= heap->bh_space) elog(ERROR, "out of binary heap slots"); heap->bh_has_heap_property = false; - heap->bh_nodes[heap->bh_size] = d; + bh_set(heap, heap->bh_size, d); heap->bh_size++; } @@ -138,11 +162,11 @@ binaryheap_build(binaryheap *heap) * the heap property. */ void -binaryheap_add(binaryheap *heap, Datum d) +binaryheap_add(binaryheap *heap, void *d) { if (heap->bh_size >= heap->bh_space) elog(ERROR, "out of binary heap slots"); - heap->bh_nodes[heap->bh_size] = d; + bh_set(heap, heap->bh_size, d); heap->bh_size++; sift_up(heap, heap->bh_size - 1); } @@ -154,11 +178,11 @@ binaryheap_add(binaryheap *heap, Datum d) * without modifying the heap. The caller must ensure that this * routine is not used on an empty heap. Always O(1). */ -Datum -binaryheap_first(binaryheap *heap) +void +binaryheap_first(binaryheap *heap, void *result) { Assert(!binaryheap_empty(heap) && heap->bh_has_heap_property); - return heap->bh_nodes[0]; + memmove(result, heap->bh_nodes, heap->bh_elem_size); } /* @@ -169,31 +193,28 @@ binaryheap_first(binaryheap *heap) * that this routine is not used on an empty heap. O(log n) worst * case. */ -Datum -binaryheap_remove_first(binaryheap *heap) +void +binaryheap_remove_first(binaryheap *heap, void *result) { - Datum result; - Assert(!binaryheap_empty(heap) && heap->bh_has_heap_property); /* extract the root node, which will be the result */ - result = heap->bh_nodes[0]; + if (result) + memmove(result, heap->bh_nodes, heap->bh_elem_size); /* easy if heap contains one element */ if (heap->bh_size == 1) { heap->bh_size--; - return result; + return; } /* * Remove the last node, placing it in the vacated root entry, and sift * the new root node down to its correct position. */ - heap->bh_nodes[0] = heap->bh_nodes[--heap->bh_size]; + bh_set(heap, 0, bh_node(heap, --heap->bh_size)); sift_down(heap, 0); - - return result; } /* @@ -204,11 +225,11 @@ binaryheap_remove_first(binaryheap *heap) * sifting the new node down. */ void -binaryheap_replace_first(binaryheap *heap, Datum d) +binaryheap_replace_first(binaryheap *heap, void *d) { Assert(!binaryheap_empty(heap) && heap->bh_has_heap_property); - heap->bh_nodes[0] = d; + bh_set(heap, 0, d); if (heap->bh_size > 1) sift_down(heap, 0); @@ -221,26 +242,26 @@ binaryheap_replace_first(binaryheap *heap, Datum d) static void sift_up(binaryheap *heap, int node_off) { - Datum node_val = heap->bh_nodes[node_off]; + memmove(heap->bh_hole, bh_node(heap, node_off), heap->bh_elem_size); /* * Within the loop, the node_off'th array entry is a "hole" that - * notionally holds node_val, but we don't actually store node_val there - * till the end, saving some unnecessary data copying steps. + * notionally holds the swapped value, but we don't actually store the + * value there till the end, saving some unnecessary data copying steps. */ while (node_off != 0) { int cmp; int parent_off; - Datum parent_val; + void *parent_val; /* * If this node is smaller than its parent, the heap condition is * satisfied, and we're done. */ parent_off = parent_offset(node_off); - parent_val = heap->bh_nodes[parent_off]; - cmp = heap->bh_compare(node_val, + parent_val = bh_node(heap, parent_off); + cmp = heap->bh_compare(heap->bh_hole, parent_val, heap->bh_arg); if (cmp <= 0) @@ -250,11 +271,11 @@ sift_up(binaryheap *heap, int node_off) * Otherwise, swap the parent value with the hole, and go on to check * the node's new parent. */ - heap->bh_nodes[node_off] = parent_val; + bh_set(heap, node_off, parent_val); node_off = parent_off; } /* Re-fill the hole */ - heap->bh_nodes[node_off] = node_val; + bh_set(heap, node_off, heap->bh_hole); } /* @@ -264,12 +285,12 @@ sift_up(binaryheap *heap, int node_off) static void sift_down(binaryheap *heap, int node_off) { - Datum node_val = heap->bh_nodes[node_off]; + memmove(heap->bh_hole, bh_node(heap, node_off), heap->bh_elem_size); /* * Within the loop, the node_off'th array entry is a "hole" that - * notionally holds node_val, but we don't actually store node_val there - * till the end, saving some unnecessary data copying steps. + * notionally holds the swapped value, but we don't actually store the + * value there till the end, saving some unnecessary data copying steps. */ while (true) { @@ -279,21 +300,21 @@ sift_down(binaryheap *heap, int node_off) /* Is the left child larger than the parent? */ if (left_off < heap->bh_size && - heap->bh_compare(node_val, - heap->bh_nodes[left_off], + heap->bh_compare(heap->bh_hole, + bh_node(heap, left_off), heap->bh_arg) < 0) swap_off = left_off; /* Is the right child larger than the parent? */ if (right_off < heap->bh_size && - heap->bh_compare(node_val, - heap->bh_nodes[right_off], + heap->bh_compare(heap->bh_hole, + bh_node(heap, right_off), heap->bh_arg) < 0) { /* swap with the larger child */ if (!swap_off || - heap->bh_compare(heap->bh_nodes[left_off], - heap->bh_nodes[right_off], + heap->bh_compare(bh_node(heap, left_off), + bh_node(heap, right_off), heap->bh_arg) < 0) swap_off = right_off; } @@ -309,9 +330,9 @@ sift_down(binaryheap *heap, int node_off) * Otherwise, swap the hole with the child that violates the heap * property; then go on to check its children. */ - heap->bh_nodes[node_off] = heap->bh_nodes[swap_off]; + bh_set(heap, node_off, bh_node(heap, swap_off)); node_off = swap_off; } /* Re-fill the hole */ - heap->bh_nodes[node_off] = node_val; + bh_set(heap, node_off, heap->bh_hole); } diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c index 46af349564..f7ee95d8f9 100644 --- a/src/backend/postmaster/pgarch.c +++ b/src/backend/postmaster/pgarch.c @@ -145,7 +145,7 @@ static bool pgarch_readyXlog(char *xlog); static void pgarch_archiveDone(char *xlog); static void pgarch_die(int code, Datum arg); static void HandlePgArchInterrupts(void); -static int ready_file_comparator(Datum a, Datum b, void *arg); +static int ready_file_comparator(const void *a, const void *b, void *arg); static void LoadArchiveLibrary(void); static void pgarch_call_module_shutdown_cb(int code, Datum arg); @@ -250,6 +250,7 @@ PgArchiverMain(void) /* Initialize our max-heap for prioritizing files to archive. */ arch_files->arch_heap = binaryheap_allocate(NUM_FILES_PER_DIRECTORY_SCAN, + sizeof(char *), ready_file_comparator, NULL); /* Load the archive_library. */ @@ -631,22 +632,27 @@ pgarch_readyXlog(char *xlog) /* If the heap isn't full yet, quickly add it. */ arch_file = arch_files->arch_filenames[arch_files->arch_heap->bh_size]; strcpy(arch_file, basename); - binaryheap_add_unordered(arch_files->arch_heap, CStringGetDatum(arch_file)); + binaryheap_add_unordered(arch_files->arch_heap, &arch_file); /* If we just filled the heap, make it a valid one. */ if (arch_files->arch_heap->bh_size == NUM_FILES_PER_DIRECTORY_SCAN) binaryheap_build(arch_files->arch_heap); } - else if (ready_file_comparator(binaryheap_first(arch_files->arch_heap), - CStringGetDatum(basename), NULL) > 0) + else { - /* - * Remove the lowest priority file and add the current one to the - * heap. - */ - arch_file = DatumGetCString(binaryheap_remove_first(arch_files->arch_heap)); - strcpy(arch_file, basename); - binaryheap_add(arch_files->arch_heap, CStringGetDatum(arch_file)); + char *root; + + binaryheap_first(arch_files->arch_heap, &root); + if (ready_file_comparator(&root, &basename, NULL) > 0) + { + /* + * Remove the lowest priority file and add the current one to + * the heap. + */ + binaryheap_remove_first(arch_files->arch_heap, &arch_file); + strcpy(arch_file, basename); + binaryheap_add(arch_files->arch_heap, &arch_file); + } } } FreeDir(rldir); @@ -668,7 +674,7 @@ pgarch_readyXlog(char *xlog) */ arch_files->arch_files_size = arch_files->arch_heap->bh_size; for (int i = 0; i < arch_files->arch_files_size; i++) - arch_files->arch_files[i] = DatumGetCString(binaryheap_remove_first(arch_files->arch_heap)); + binaryheap_remove_first(arch_files->arch_heap, &arch_files->arch_files[i]); /* Return the highest priority file. */ arch_files->arch_files_size--; @@ -686,10 +692,10 @@ pgarch_readyXlog(char *xlog) * If "a" and "b" have equivalent values, 0 will be returned. */ static int -ready_file_comparator(Datum a, Datum b, void *arg) +ready_file_comparator(const void *a, const void *b, void *arg) { - char *a_str = DatumGetCString(a); - char *b_str = DatumGetCString(b); + const char *a_str = *((const char **) a); + const char *b_str = *((const char **) b); bool a_history = IsTLHistoryFileName(a_str); bool b_history = IsTLHistoryFileName(b_str); diff --git a/src/backend/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c index 12edc5772a..e22680da0b 100644 --- a/src/backend/replication/logical/reorderbuffer.c +++ b/src/backend/replication/logical/reorderbuffer.c @@ -1223,11 +1223,11 @@ ReorderBufferCommitChild(ReorderBuffer *rb, TransactionId xid, * Binary heap comparison function. */ static int -ReorderBufferIterCompare(Datum a, Datum b, void *arg) +ReorderBufferIterCompare(const void *a, const void *b, void *arg) { ReorderBufferIterTXNState *state = (ReorderBufferIterTXNState *) arg; - XLogRecPtr pos_a = state->entries[DatumGetInt32(a)].lsn; - XLogRecPtr pos_b = state->entries[DatumGetInt32(b)].lsn; + XLogRecPtr pos_a = state->entries[*((const int *) a)].lsn; + XLogRecPtr pos_b = state->entries[*((const int *) b)].lsn; if (pos_a < pos_b) return 1; @@ -1297,6 +1297,7 @@ ReorderBufferIterTXNInit(ReorderBuffer *rb, ReorderBufferTXN *txn, /* allocate heap */ state->heap = binaryheap_allocate(state->nr_txns, + sizeof(int), ReorderBufferIterCompare, state); @@ -1330,7 +1331,8 @@ ReorderBufferIterTXNInit(ReorderBuffer *rb, ReorderBufferTXN *txn, state->entries[off].change = cur_change; state->entries[off].txn = txn; - binaryheap_add_unordered(state->heap, Int32GetDatum(off++)); + binaryheap_add_unordered(state->heap, &off); + off++; } /* add subtransactions if they contain changes */ @@ -1359,7 +1361,8 @@ ReorderBufferIterTXNInit(ReorderBuffer *rb, ReorderBufferTXN *txn, state->entries[off].change = cur_change; state->entries[off].txn = cur_txn; - binaryheap_add_unordered(state->heap, Int32GetDatum(off++)); + binaryheap_add_unordered(state->heap, &off); + off++; } } @@ -1384,7 +1387,7 @@ ReorderBufferIterTXNNext(ReorderBuffer *rb, ReorderBufferIterTXNState *state) if (state->heap->bh_size == 0) return NULL; - off = DatumGetInt32(binaryheap_first(state->heap)); + binaryheap_first(state->heap, &off); entry = &state->entries[off]; /* free memory we might have "leaked" in the previous *Next call */ @@ -1414,7 +1417,7 @@ ReorderBufferIterTXNNext(ReorderBuffer *rb, ReorderBufferIterTXNState *state) state->entries[off].lsn = next_change->lsn; state->entries[off].change = next_change; - binaryheap_replace_first(state->heap, Int32GetDatum(off)); + binaryheap_replace_first(state->heap, &off); return change; } @@ -1450,14 +1453,14 @@ ReorderBufferIterTXNNext(ReorderBuffer *rb, ReorderBufferIterTXNState *state) /* txn stays the same */ state->entries[off].lsn = next_change->lsn; state->entries[off].change = next_change; - binaryheap_replace_first(state->heap, Int32GetDatum(off)); + binaryheap_replace_first(state->heap, &off); return change; } } /* ok, no changes there anymore, remove */ - binaryheap_remove_first(state->heap); + binaryheap_remove_first(state->heap, NULL); return change; } diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 3bd82dbfca..7c78a0d625 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -501,7 +501,7 @@ static void CheckForBufferLeaks(void); static int rlocator_comparator(const void *p1, const void *p2); static inline int buffertag_comparator(const BufferTag *ba, const BufferTag *bb); static inline int ckpt_buforder_comparator(const CkptSortItem *a, const CkptSortItem *b); -static int ts_ckpt_progress_comparator(Datum a, Datum b, void *arg); +static int ts_ckpt_progress_comparator(const void *a, const void *b, void *arg); /* @@ -2640,6 +2640,7 @@ BufferSync(int flags) * processed buffer is. */ ts_heap = binaryheap_allocate(num_spaces, + sizeof(CkptTsStatus *), ts_ckpt_progress_comparator, NULL); @@ -2649,7 +2650,7 @@ BufferSync(int flags) ts_stat->progress_slice = (float8) num_to_scan / ts_stat->num_to_scan; - binaryheap_add_unordered(ts_heap, PointerGetDatum(ts_stat)); + binaryheap_add_unordered(ts_heap, &ts_stat); } binaryheap_build(ts_heap); @@ -2665,8 +2666,9 @@ BufferSync(int flags) while (!binaryheap_empty(ts_heap)) { BufferDesc *bufHdr = NULL; - CkptTsStatus *ts_stat = (CkptTsStatus *) - DatumGetPointer(binaryheap_first(ts_heap)); + CkptTsStatus *ts_stat; + + binaryheap_first(ts_heap, &ts_stat); buf_id = CkptBufferIds[ts_stat->index].buf_id; Assert(buf_id != -1); @@ -2708,12 +2710,12 @@ BufferSync(int flags) /* Have all the buffers from the tablespace been processed? */ if (ts_stat->num_scanned == ts_stat->num_to_scan) { - binaryheap_remove_first(ts_heap); + binaryheap_remove_first(ts_heap, NULL); } else { /* update heap with the new progress */ - binaryheap_replace_first(ts_heap, PointerGetDatum(ts_stat)); + binaryheap_replace_first(ts_heap, &ts_stat); } /* @@ -5416,10 +5418,10 @@ ckpt_buforder_comparator(const CkptSortItem *a, const CkptSortItem *b) * progress. */ static int -ts_ckpt_progress_comparator(Datum a, Datum b, void *arg) +ts_ckpt_progress_comparator(const void *a, const void *b, void *arg) { - CkptTsStatus *sa = (CkptTsStatus *) a; - CkptTsStatus *sb = (CkptTsStatus *) b; + const CkptTsStatus *sa = *((const CkptTsStatus **) a); + const CkptTsStatus *sb = *((const CkptTsStatus **) b); /* we want a min-heap, so return 1 for the a < b */ if (sa->progress < sb->progress) diff --git a/src/include/lib/binaryheap.h b/src/include/lib/binaryheap.h index 52f7b06b25..b8c7ef81ef 100644 --- a/src/include/lib/binaryheap.h +++ b/src/include/lib/binaryheap.h @@ -15,7 +15,7 @@ * For a max-heap, the comparator must return <0 iff a < b, 0 iff a == b, * and >0 iff a > b. For a min-heap, the conditions are reversed. */ -typedef int (*binaryheap_comparator) (Datum a, Datum b, void *arg); +typedef int (*binaryheap_comparator) (const void *a, const void *b, void *arg); /* * binaryheap @@ -25,29 +25,32 @@ typedef int (*binaryheap_comparator) (Datum a, Datum b, void *arg); * bh_has_heap_property no unordered operations since last heap build * bh_compare comparison function to define the heap property * bh_arg user data for comparison function - * bh_nodes variable-length array of "space" nodes + * bh_hole extra node used during sifting operations + * bh_nodes variable-length array of "space + 1" nodes */ typedef struct binaryheap { int bh_size; int bh_space; + size_t bh_elem_size; bool bh_has_heap_property; /* debugging cross-check */ binaryheap_comparator bh_compare; void *bh_arg; - Datum bh_nodes[FLEXIBLE_ARRAY_MEMBER]; + void *bh_hole; + char bh_nodes[FLEXIBLE_ARRAY_MEMBER]; } binaryheap; -extern binaryheap *binaryheap_allocate(int capacity, +extern binaryheap *binaryheap_allocate(int capacity, size_t elem_size, binaryheap_comparator compare, void *arg); extern void binaryheap_reset(binaryheap *heap); extern void binaryheap_free(binaryheap *heap); -extern void binaryheap_add_unordered(binaryheap *heap, Datum d); +extern void binaryheap_add_unordered(binaryheap *heap, void *d); extern void binaryheap_build(binaryheap *heap); -extern void binaryheap_add(binaryheap *heap, Datum d); -extern Datum binaryheap_first(binaryheap *heap); -extern Datum binaryheap_remove_first(binaryheap *heap); -extern void binaryheap_replace_first(binaryheap *heap, Datum d); +extern void binaryheap_add(binaryheap *heap, void *d); +extern void binaryheap_first(binaryheap *heap, void *result); +extern void binaryheap_remove_first(binaryheap *heap, void *result); +extern void binaryheap_replace_first(binaryheap *heap, void *d); #define binaryheap_empty(h) ((h)->bh_size == 0) -- 2.25.1 --DocE+STaALJfprDB Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v7-0002-Make-binaryheap-available-to-frontend-code.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v7 1/5] Allow binaryheap to use any type. @ 2023-09-04 22:04 Nathan Bossart <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nathan Bossart @ 2023-09-04 22:04 UTC (permalink / raw) We would like to make binaryheap available to frontend code in a future commit, but presently the implementation uses Datum for the node type, which is inaccessible in the frontend. This commit modifies the implementation to allow using any type for the nodes. binaryheap_allocate() now requires callers to specify the size of the node, and several of the other functions now use void pointers. --- src/backend/executor/nodeGatherMerge.c | 19 ++-- src/backend/executor/nodeMergeAppend.c | 22 ++--- src/backend/lib/binaryheap.c | 99 +++++++++++-------- src/backend/postmaster/pgarch.c | 36 ++++--- .../replication/logical/reorderbuffer.c | 21 ++-- src/backend/storage/buffer/bufmgr.c | 20 ++-- src/include/lib/binaryheap.h | 21 ++-- 7 files changed, 137 insertions(+), 101 deletions(-) diff --git a/src/backend/executor/nodeGatherMerge.c b/src/backend/executor/nodeGatherMerge.c index 9d5e1a46e9..f76406b575 100644 --- a/src/backend/executor/nodeGatherMerge.c +++ b/src/backend/executor/nodeGatherMerge.c @@ -52,7 +52,7 @@ typedef struct GMReaderTupleBuffer } GMReaderTupleBuffer; static TupleTableSlot *ExecGatherMerge(PlanState *pstate); -static int32 heap_compare_slots(Datum a, Datum b, void *arg); +static int32 heap_compare_slots(const void *a, const void *b, void *arg); static TupleTableSlot *gather_merge_getnext(GatherMergeState *gm_state); static MinimalTuple gm_readnext_tuple(GatherMergeState *gm_state, int nreader, bool nowait, bool *done); @@ -429,6 +429,7 @@ gather_merge_setup(GatherMergeState *gm_state) /* Allocate the resources for the merge */ gm_state->gm_heap = binaryheap_allocate(nreaders + 1, + sizeof(int), heap_compare_slots, gm_state); } @@ -489,7 +490,7 @@ reread: /* Don't have a tuple yet, try to get one */ if (gather_merge_readnext(gm_state, i, nowait)) binaryheap_add_unordered(gm_state->gm_heap, - Int32GetDatum(i)); + &i); } else { @@ -565,14 +566,14 @@ gather_merge_getnext(GatherMergeState *gm_state) * the heap, because it might now compare differently against the * other elements of the heap. */ - i = DatumGetInt32(binaryheap_first(gm_state->gm_heap)); + binaryheap_first(gm_state->gm_heap, &i); if (gather_merge_readnext(gm_state, i, false)) - binaryheap_replace_first(gm_state->gm_heap, Int32GetDatum(i)); + binaryheap_replace_first(gm_state->gm_heap, &i); else { /* reader exhausted, remove it from heap */ - (void) binaryheap_remove_first(gm_state->gm_heap); + binaryheap_remove_first(gm_state->gm_heap, NULL); } } @@ -585,7 +586,7 @@ gather_merge_getnext(GatherMergeState *gm_state) else { /* Return next tuple from whichever participant has the leading one */ - i = DatumGetInt32(binaryheap_first(gm_state->gm_heap)); + binaryheap_first(gm_state->gm_heap, &i); return gm_state->gm_slots[i]; } } @@ -750,11 +751,11 @@ typedef int32 SlotNumber; * Compare the tuples in the two given slots. */ static int32 -heap_compare_slots(Datum a, Datum b, void *arg) +heap_compare_slots(const void *a, const void *b, void *arg) { GatherMergeState *node = (GatherMergeState *) arg; - SlotNumber slot1 = DatumGetInt32(a); - SlotNumber slot2 = DatumGetInt32(b); + SlotNumber slot1 = *((const int *) a); + SlotNumber slot2 = *((const int *) b); TupleTableSlot *s1 = node->gm_slots[slot1]; TupleTableSlot *s2 = node->gm_slots[slot2]; diff --git a/src/backend/executor/nodeMergeAppend.c b/src/backend/executor/nodeMergeAppend.c index 21b5726e6e..e0ace294a0 100644 --- a/src/backend/executor/nodeMergeAppend.c +++ b/src/backend/executor/nodeMergeAppend.c @@ -52,7 +52,7 @@ typedef int32 SlotNumber; static TupleTableSlot *ExecMergeAppend(PlanState *pstate); -static int heap_compare_slots(Datum a, Datum b, void *arg); +static int heap_compare_slots(const void *a, const void *b, void *arg); /* ---------------------------------------------------------------- @@ -125,8 +125,8 @@ ExecInitMergeAppend(MergeAppend *node, EState *estate, int eflags) mergestate->ms_nplans = nplans; mergestate->ms_slots = (TupleTableSlot **) palloc0(sizeof(TupleTableSlot *) * nplans); - mergestate->ms_heap = binaryheap_allocate(nplans, heap_compare_slots, - mergestate); + mergestate->ms_heap = binaryheap_allocate(nplans, sizeof(int), + heap_compare_slots, mergestate); /* * Miscellaneous initialization @@ -229,7 +229,7 @@ ExecMergeAppend(PlanState *pstate) { node->ms_slots[i] = ExecProcNode(node->mergeplans[i]); if (!TupIsNull(node->ms_slots[i])) - binaryheap_add_unordered(node->ms_heap, Int32GetDatum(i)); + binaryheap_add_unordered(node->ms_heap, &i); } binaryheap_build(node->ms_heap); node->ms_initialized = true; @@ -244,12 +244,12 @@ ExecMergeAppend(PlanState *pstate) * by doing this before returning from the prior call, but it's better * to not pull tuples until necessary.) */ - i = DatumGetInt32(binaryheap_first(node->ms_heap)); + binaryheap_first(node->ms_heap, &i); node->ms_slots[i] = ExecProcNode(node->mergeplans[i]); if (!TupIsNull(node->ms_slots[i])) - binaryheap_replace_first(node->ms_heap, Int32GetDatum(i)); + binaryheap_replace_first(node->ms_heap, &i); else - (void) binaryheap_remove_first(node->ms_heap); + binaryheap_remove_first(node->ms_heap, NULL); } if (binaryheap_empty(node->ms_heap)) @@ -259,7 +259,7 @@ ExecMergeAppend(PlanState *pstate) } else { - i = DatumGetInt32(binaryheap_first(node->ms_heap)); + binaryheap_first(node->ms_heap, &i); result = node->ms_slots[i]; } @@ -270,11 +270,11 @@ ExecMergeAppend(PlanState *pstate) * Compare the tuples in the two given slots. */ static int32 -heap_compare_slots(Datum a, Datum b, void *arg) +heap_compare_slots(const void *a, const void *b, void *arg) { MergeAppendState *node = (MergeAppendState *) arg; - SlotNumber slot1 = DatumGetInt32(a); - SlotNumber slot2 = DatumGetInt32(b); + SlotNumber slot1 = *((const int *) a); + SlotNumber slot2 = *((const int *) b); TupleTableSlot *s1 = node->ms_slots[slot1]; TupleTableSlot *s2 = node->ms_slots[slot2]; diff --git a/src/backend/lib/binaryheap.c b/src/backend/lib/binaryheap.c index 1737546757..6f63181c4c 100644 --- a/src/backend/lib/binaryheap.c +++ b/src/backend/lib/binaryheap.c @@ -29,16 +29,19 @@ static void sift_up(binaryheap *heap, int node_off); * argument specified by 'arg'. */ binaryheap * -binaryheap_allocate(int capacity, binaryheap_comparator compare, void *arg) +binaryheap_allocate(int capacity, size_t elem_size, + binaryheap_comparator compare, void *arg) { int sz; binaryheap *heap; - sz = offsetof(binaryheap, bh_nodes) + sizeof(Datum) * capacity; + sz = offsetof(binaryheap, bh_nodes) + elem_size * (capacity + 1); heap = (binaryheap *) palloc(sz); heap->bh_space = capacity; + heap->bh_elem_size = elem_size; heap->bh_compare = compare; heap->bh_arg = arg; + heap->bh_hole = &heap->bh_nodes[capacity * elem_size]; heap->bh_size = 0; heap->bh_has_heap_property = true; @@ -97,6 +100,27 @@ parent_offset(int i) return (i - 1) / 2; } +/* + * This utility function returns a pointer to the nth node of the binary heap. + */ +static inline void * +bh_node(binaryheap *heap, int n) +{ + return &heap->bh_nodes[n * heap->bh_elem_size]; +} + +/* + * This utility function sets the nth node of the binary heap to the value that + * d points to. + */ +static inline void +bh_set(binaryheap *heap, int n, const void *d) +{ + void *node = bh_node(heap, n); + + memmove(node, d, heap->bh_elem_size); +} + /* * binaryheap_add_unordered * @@ -106,12 +130,12 @@ parent_offset(int i) * afterwards. */ void -binaryheap_add_unordered(binaryheap *heap, Datum d) +binaryheap_add_unordered(binaryheap *heap, void *d) { if (heap->bh_size >= heap->bh_space) elog(ERROR, "out of binary heap slots"); heap->bh_has_heap_property = false; - heap->bh_nodes[heap->bh_size] = d; + bh_set(heap, heap->bh_size, d); heap->bh_size++; } @@ -138,11 +162,11 @@ binaryheap_build(binaryheap *heap) * the heap property. */ void -binaryheap_add(binaryheap *heap, Datum d) +binaryheap_add(binaryheap *heap, void *d) { if (heap->bh_size >= heap->bh_space) elog(ERROR, "out of binary heap slots"); - heap->bh_nodes[heap->bh_size] = d; + bh_set(heap, heap->bh_size, d); heap->bh_size++; sift_up(heap, heap->bh_size - 1); } @@ -154,11 +178,11 @@ binaryheap_add(binaryheap *heap, Datum d) * without modifying the heap. The caller must ensure that this * routine is not used on an empty heap. Always O(1). */ -Datum -binaryheap_first(binaryheap *heap) +void +binaryheap_first(binaryheap *heap, void *result) { Assert(!binaryheap_empty(heap) && heap->bh_has_heap_property); - return heap->bh_nodes[0]; + memmove(result, heap->bh_nodes, heap->bh_elem_size); } /* @@ -169,31 +193,28 @@ binaryheap_first(binaryheap *heap) * that this routine is not used on an empty heap. O(log n) worst * case. */ -Datum -binaryheap_remove_first(binaryheap *heap) +void +binaryheap_remove_first(binaryheap *heap, void *result) { - Datum result; - Assert(!binaryheap_empty(heap) && heap->bh_has_heap_property); /* extract the root node, which will be the result */ - result = heap->bh_nodes[0]; + if (result) + memmove(result, heap->bh_nodes, heap->bh_elem_size); /* easy if heap contains one element */ if (heap->bh_size == 1) { heap->bh_size--; - return result; + return; } /* * Remove the last node, placing it in the vacated root entry, and sift * the new root node down to its correct position. */ - heap->bh_nodes[0] = heap->bh_nodes[--heap->bh_size]; + bh_set(heap, 0, bh_node(heap, --heap->bh_size)); sift_down(heap, 0); - - return result; } /* @@ -204,11 +225,11 @@ binaryheap_remove_first(binaryheap *heap) * sifting the new node down. */ void -binaryheap_replace_first(binaryheap *heap, Datum d) +binaryheap_replace_first(binaryheap *heap, void *d) { Assert(!binaryheap_empty(heap) && heap->bh_has_heap_property); - heap->bh_nodes[0] = d; + bh_set(heap, 0, d); if (heap->bh_size > 1) sift_down(heap, 0); @@ -221,26 +242,26 @@ binaryheap_replace_first(binaryheap *heap, Datum d) static void sift_up(binaryheap *heap, int node_off) { - Datum node_val = heap->bh_nodes[node_off]; + memmove(heap->bh_hole, bh_node(heap, node_off), heap->bh_elem_size); /* * Within the loop, the node_off'th array entry is a "hole" that - * notionally holds node_val, but we don't actually store node_val there - * till the end, saving some unnecessary data copying steps. + * notionally holds the swapped value, but we don't actually store the + * value there till the end, saving some unnecessary data copying steps. */ while (node_off != 0) { int cmp; int parent_off; - Datum parent_val; + void *parent_val; /* * If this node is smaller than its parent, the heap condition is * satisfied, and we're done. */ parent_off = parent_offset(node_off); - parent_val = heap->bh_nodes[parent_off]; - cmp = heap->bh_compare(node_val, + parent_val = bh_node(heap, parent_off); + cmp = heap->bh_compare(heap->bh_hole, parent_val, heap->bh_arg); if (cmp <= 0) @@ -250,11 +271,11 @@ sift_up(binaryheap *heap, int node_off) * Otherwise, swap the parent value with the hole, and go on to check * the node's new parent. */ - heap->bh_nodes[node_off] = parent_val; + bh_set(heap, node_off, parent_val); node_off = parent_off; } /* Re-fill the hole */ - heap->bh_nodes[node_off] = node_val; + bh_set(heap, node_off, heap->bh_hole); } /* @@ -264,12 +285,12 @@ sift_up(binaryheap *heap, int node_off) static void sift_down(binaryheap *heap, int node_off) { - Datum node_val = heap->bh_nodes[node_off]; + memmove(heap->bh_hole, bh_node(heap, node_off), heap->bh_elem_size); /* * Within the loop, the node_off'th array entry is a "hole" that - * notionally holds node_val, but we don't actually store node_val there - * till the end, saving some unnecessary data copying steps. + * notionally holds the swapped value, but we don't actually store the + * value there till the end, saving some unnecessary data copying steps. */ while (true) { @@ -279,21 +300,21 @@ sift_down(binaryheap *heap, int node_off) /* Is the left child larger than the parent? */ if (left_off < heap->bh_size && - heap->bh_compare(node_val, - heap->bh_nodes[left_off], + heap->bh_compare(heap->bh_hole, + bh_node(heap, left_off), heap->bh_arg) < 0) swap_off = left_off; /* Is the right child larger than the parent? */ if (right_off < heap->bh_size && - heap->bh_compare(node_val, - heap->bh_nodes[right_off], + heap->bh_compare(heap->bh_hole, + bh_node(heap, right_off), heap->bh_arg) < 0) { /* swap with the larger child */ if (!swap_off || - heap->bh_compare(heap->bh_nodes[left_off], - heap->bh_nodes[right_off], + heap->bh_compare(bh_node(heap, left_off), + bh_node(heap, right_off), heap->bh_arg) < 0) swap_off = right_off; } @@ -309,9 +330,9 @@ sift_down(binaryheap *heap, int node_off) * Otherwise, swap the hole with the child that violates the heap * property; then go on to check its children. */ - heap->bh_nodes[node_off] = heap->bh_nodes[swap_off]; + bh_set(heap, node_off, bh_node(heap, swap_off)); node_off = swap_off; } /* Re-fill the hole */ - heap->bh_nodes[node_off] = node_val; + bh_set(heap, node_off, heap->bh_hole); } diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c index 46af349564..f7ee95d8f9 100644 --- a/src/backend/postmaster/pgarch.c +++ b/src/backend/postmaster/pgarch.c @@ -145,7 +145,7 @@ static bool pgarch_readyXlog(char *xlog); static void pgarch_archiveDone(char *xlog); static void pgarch_die(int code, Datum arg); static void HandlePgArchInterrupts(void); -static int ready_file_comparator(Datum a, Datum b, void *arg); +static int ready_file_comparator(const void *a, const void *b, void *arg); static void LoadArchiveLibrary(void); static void pgarch_call_module_shutdown_cb(int code, Datum arg); @@ -250,6 +250,7 @@ PgArchiverMain(void) /* Initialize our max-heap for prioritizing files to archive. */ arch_files->arch_heap = binaryheap_allocate(NUM_FILES_PER_DIRECTORY_SCAN, + sizeof(char *), ready_file_comparator, NULL); /* Load the archive_library. */ @@ -631,22 +632,27 @@ pgarch_readyXlog(char *xlog) /* If the heap isn't full yet, quickly add it. */ arch_file = arch_files->arch_filenames[arch_files->arch_heap->bh_size]; strcpy(arch_file, basename); - binaryheap_add_unordered(arch_files->arch_heap, CStringGetDatum(arch_file)); + binaryheap_add_unordered(arch_files->arch_heap, &arch_file); /* If we just filled the heap, make it a valid one. */ if (arch_files->arch_heap->bh_size == NUM_FILES_PER_DIRECTORY_SCAN) binaryheap_build(arch_files->arch_heap); } - else if (ready_file_comparator(binaryheap_first(arch_files->arch_heap), - CStringGetDatum(basename), NULL) > 0) + else { - /* - * Remove the lowest priority file and add the current one to the - * heap. - */ - arch_file = DatumGetCString(binaryheap_remove_first(arch_files->arch_heap)); - strcpy(arch_file, basename); - binaryheap_add(arch_files->arch_heap, CStringGetDatum(arch_file)); + char *root; + + binaryheap_first(arch_files->arch_heap, &root); + if (ready_file_comparator(&root, &basename, NULL) > 0) + { + /* + * Remove the lowest priority file and add the current one to + * the heap. + */ + binaryheap_remove_first(arch_files->arch_heap, &arch_file); + strcpy(arch_file, basename); + binaryheap_add(arch_files->arch_heap, &arch_file); + } } } FreeDir(rldir); @@ -668,7 +674,7 @@ pgarch_readyXlog(char *xlog) */ arch_files->arch_files_size = arch_files->arch_heap->bh_size; for (int i = 0; i < arch_files->arch_files_size; i++) - arch_files->arch_files[i] = DatumGetCString(binaryheap_remove_first(arch_files->arch_heap)); + binaryheap_remove_first(arch_files->arch_heap, &arch_files->arch_files[i]); /* Return the highest priority file. */ arch_files->arch_files_size--; @@ -686,10 +692,10 @@ pgarch_readyXlog(char *xlog) * If "a" and "b" have equivalent values, 0 will be returned. */ static int -ready_file_comparator(Datum a, Datum b, void *arg) +ready_file_comparator(const void *a, const void *b, void *arg) { - char *a_str = DatumGetCString(a); - char *b_str = DatumGetCString(b); + const char *a_str = *((const char **) a); + const char *b_str = *((const char **) b); bool a_history = IsTLHistoryFileName(a_str); bool b_history = IsTLHistoryFileName(b_str); diff --git a/src/backend/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c index 12edc5772a..e22680da0b 100644 --- a/src/backend/replication/logical/reorderbuffer.c +++ b/src/backend/replication/logical/reorderbuffer.c @@ -1223,11 +1223,11 @@ ReorderBufferCommitChild(ReorderBuffer *rb, TransactionId xid, * Binary heap comparison function. */ static int -ReorderBufferIterCompare(Datum a, Datum b, void *arg) +ReorderBufferIterCompare(const void *a, const void *b, void *arg) { ReorderBufferIterTXNState *state = (ReorderBufferIterTXNState *) arg; - XLogRecPtr pos_a = state->entries[DatumGetInt32(a)].lsn; - XLogRecPtr pos_b = state->entries[DatumGetInt32(b)].lsn; + XLogRecPtr pos_a = state->entries[*((const int *) a)].lsn; + XLogRecPtr pos_b = state->entries[*((const int *) b)].lsn; if (pos_a < pos_b) return 1; @@ -1297,6 +1297,7 @@ ReorderBufferIterTXNInit(ReorderBuffer *rb, ReorderBufferTXN *txn, /* allocate heap */ state->heap = binaryheap_allocate(state->nr_txns, + sizeof(int), ReorderBufferIterCompare, state); @@ -1330,7 +1331,8 @@ ReorderBufferIterTXNInit(ReorderBuffer *rb, ReorderBufferTXN *txn, state->entries[off].change = cur_change; state->entries[off].txn = txn; - binaryheap_add_unordered(state->heap, Int32GetDatum(off++)); + binaryheap_add_unordered(state->heap, &off); + off++; } /* add subtransactions if they contain changes */ @@ -1359,7 +1361,8 @@ ReorderBufferIterTXNInit(ReorderBuffer *rb, ReorderBufferTXN *txn, state->entries[off].change = cur_change; state->entries[off].txn = cur_txn; - binaryheap_add_unordered(state->heap, Int32GetDatum(off++)); + binaryheap_add_unordered(state->heap, &off); + off++; } } @@ -1384,7 +1387,7 @@ ReorderBufferIterTXNNext(ReorderBuffer *rb, ReorderBufferIterTXNState *state) if (state->heap->bh_size == 0) return NULL; - off = DatumGetInt32(binaryheap_first(state->heap)); + binaryheap_first(state->heap, &off); entry = &state->entries[off]; /* free memory we might have "leaked" in the previous *Next call */ @@ -1414,7 +1417,7 @@ ReorderBufferIterTXNNext(ReorderBuffer *rb, ReorderBufferIterTXNState *state) state->entries[off].lsn = next_change->lsn; state->entries[off].change = next_change; - binaryheap_replace_first(state->heap, Int32GetDatum(off)); + binaryheap_replace_first(state->heap, &off); return change; } @@ -1450,14 +1453,14 @@ ReorderBufferIterTXNNext(ReorderBuffer *rb, ReorderBufferIterTXNState *state) /* txn stays the same */ state->entries[off].lsn = next_change->lsn; state->entries[off].change = next_change; - binaryheap_replace_first(state->heap, Int32GetDatum(off)); + binaryheap_replace_first(state->heap, &off); return change; } } /* ok, no changes there anymore, remove */ - binaryheap_remove_first(state->heap); + binaryheap_remove_first(state->heap, NULL); return change; } diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 3bd82dbfca..7c78a0d625 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -501,7 +501,7 @@ static void CheckForBufferLeaks(void); static int rlocator_comparator(const void *p1, const void *p2); static inline int buffertag_comparator(const BufferTag *ba, const BufferTag *bb); static inline int ckpt_buforder_comparator(const CkptSortItem *a, const CkptSortItem *b); -static int ts_ckpt_progress_comparator(Datum a, Datum b, void *arg); +static int ts_ckpt_progress_comparator(const void *a, const void *b, void *arg); /* @@ -2640,6 +2640,7 @@ BufferSync(int flags) * processed buffer is. */ ts_heap = binaryheap_allocate(num_spaces, + sizeof(CkptTsStatus *), ts_ckpt_progress_comparator, NULL); @@ -2649,7 +2650,7 @@ BufferSync(int flags) ts_stat->progress_slice = (float8) num_to_scan / ts_stat->num_to_scan; - binaryheap_add_unordered(ts_heap, PointerGetDatum(ts_stat)); + binaryheap_add_unordered(ts_heap, &ts_stat); } binaryheap_build(ts_heap); @@ -2665,8 +2666,9 @@ BufferSync(int flags) while (!binaryheap_empty(ts_heap)) { BufferDesc *bufHdr = NULL; - CkptTsStatus *ts_stat = (CkptTsStatus *) - DatumGetPointer(binaryheap_first(ts_heap)); + CkptTsStatus *ts_stat; + + binaryheap_first(ts_heap, &ts_stat); buf_id = CkptBufferIds[ts_stat->index].buf_id; Assert(buf_id != -1); @@ -2708,12 +2710,12 @@ BufferSync(int flags) /* Have all the buffers from the tablespace been processed? */ if (ts_stat->num_scanned == ts_stat->num_to_scan) { - binaryheap_remove_first(ts_heap); + binaryheap_remove_first(ts_heap, NULL); } else { /* update heap with the new progress */ - binaryheap_replace_first(ts_heap, PointerGetDatum(ts_stat)); + binaryheap_replace_first(ts_heap, &ts_stat); } /* @@ -5416,10 +5418,10 @@ ckpt_buforder_comparator(const CkptSortItem *a, const CkptSortItem *b) * progress. */ static int -ts_ckpt_progress_comparator(Datum a, Datum b, void *arg) +ts_ckpt_progress_comparator(const void *a, const void *b, void *arg) { - CkptTsStatus *sa = (CkptTsStatus *) a; - CkptTsStatus *sb = (CkptTsStatus *) b; + const CkptTsStatus *sa = *((const CkptTsStatus **) a); + const CkptTsStatus *sb = *((const CkptTsStatus **) b); /* we want a min-heap, so return 1 for the a < b */ if (sa->progress < sb->progress) diff --git a/src/include/lib/binaryheap.h b/src/include/lib/binaryheap.h index 52f7b06b25..b8c7ef81ef 100644 --- a/src/include/lib/binaryheap.h +++ b/src/include/lib/binaryheap.h @@ -15,7 +15,7 @@ * For a max-heap, the comparator must return <0 iff a < b, 0 iff a == b, * and >0 iff a > b. For a min-heap, the conditions are reversed. */ -typedef int (*binaryheap_comparator) (Datum a, Datum b, void *arg); +typedef int (*binaryheap_comparator) (const void *a, const void *b, void *arg); /* * binaryheap @@ -25,29 +25,32 @@ typedef int (*binaryheap_comparator) (Datum a, Datum b, void *arg); * bh_has_heap_property no unordered operations since last heap build * bh_compare comparison function to define the heap property * bh_arg user data for comparison function - * bh_nodes variable-length array of "space" nodes + * bh_hole extra node used during sifting operations + * bh_nodes variable-length array of "space + 1" nodes */ typedef struct binaryheap { int bh_size; int bh_space; + size_t bh_elem_size; bool bh_has_heap_property; /* debugging cross-check */ binaryheap_comparator bh_compare; void *bh_arg; - Datum bh_nodes[FLEXIBLE_ARRAY_MEMBER]; + void *bh_hole; + char bh_nodes[FLEXIBLE_ARRAY_MEMBER]; } binaryheap; -extern binaryheap *binaryheap_allocate(int capacity, +extern binaryheap *binaryheap_allocate(int capacity, size_t elem_size, binaryheap_comparator compare, void *arg); extern void binaryheap_reset(binaryheap *heap); extern void binaryheap_free(binaryheap *heap); -extern void binaryheap_add_unordered(binaryheap *heap, Datum d); +extern void binaryheap_add_unordered(binaryheap *heap, void *d); extern void binaryheap_build(binaryheap *heap); -extern void binaryheap_add(binaryheap *heap, Datum d); -extern Datum binaryheap_first(binaryheap *heap); -extern Datum binaryheap_remove_first(binaryheap *heap); -extern void binaryheap_replace_first(binaryheap *heap, Datum d); +extern void binaryheap_add(binaryheap *heap, void *d); +extern void binaryheap_first(binaryheap *heap, void *result); +extern void binaryheap_remove_first(binaryheap *heap, void *result); +extern void binaryheap_replace_first(binaryheap *heap, void *d); #define binaryheap_empty(h) ((h)->bh_size == 0) -- 2.25.1 --DocE+STaALJfprDB Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v7-0002-Make-binaryheap-available-to-frontend-code.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart5129932.5fSG56mABF Content-Disposition: attachment; filename="v2-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v2a 2/4] Introduce ternary reloptions @ 2025-09-04 14:41 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Nikolay Shaplov @ 2025-09-04 14:41 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TERNARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68f..f543f11001 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) allocate_reloption(kinds, + RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption = init_ternary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, ternary default_val, + int offset) +{ + relopt_ternary *newoption = init_ternary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7..977babff54 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c..a436697658 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9d..9c59ccccf8 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * ternary + * Boolean value with an extrea "unset" option + * + * Ternary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} ternary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915..7346d618f9 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart4155600.3ZeAukHxDK Content-Disposition: attachment; filename="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v2a-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
* [PATCH v3] Introduce ternary reloptions @ 2026-01-16 15:04 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 853+ messages in thread From: Álvaro Herrera @ 2026-01-16 15:04 UTC (permalink / raw) Introduce ternary reloption as a replacement for current `vacuum_truncate` implementation. Remove the `vacuum_truncate_set` separate flag and use TERNARY_UNSET instead. This could also be used for other options such as `vacuum_index_cleanup` and `buffering`, but lets get the scaffolding in first. Discussion: https://postgr.es/m/3474141.usfYGdeWWP@thinkpad-pgpro --- src/backend/access/common/reloptions.c | 137 ++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 27 ++--- src/include/postgres.h | 15 +++ src/include/utils/rel.h | 3 +- src/test/regress/expected/reloptions.out | 36 ++++++ src/test/regress/sql/reloptions.sql | 21 ++++ src/tools/pgindent/typedefs.list | 2 + 8 files changed, 202 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0b83f98ed5f..6f1f577581a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, ternary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,25 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_ternary ternaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TERNARY_UNSET + }, + /* list terminator */ + { + { + NULL + } + } +}; + static relopt_int intRelOpts[] = { { @@ -609,6 +619,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(ternaryRelOpts[i].gen.lockmode, + ternaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -649,6 +666,14 @@ initialize_reloptions(void) j++; } + for (i = 0; ternaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &ternaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TERNARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -809,6 +834,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TERNARY: + size = sizeof(relopt_ternary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -892,6 +920,57 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_ternary_reloption + * Allocate and initialize a new ternary reloption + */ +static relopt_ternary * +init_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = (relopt_ternary *) + allocate_reloption(kinds, RELOPT_TYPE_TERNARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_ternary_reloption + * Add a new ternary reloption + */ +void +add_ternary_reloption(bits32 kinds, const char *name, const char *desc, + pg_ternary default_val, LOCKMODE lockmode) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(kinds, name, desc, default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_ternary_reloption + * Add a new ternary local reloption + * + * 'offset' is offset of ternary-typed field. + */ +void +add_local_ternary_reloption(local_relopts *relopts, const char *name, + const char *desc, pg_ternary default_val, + int offset) +{ + relopt_ternary *newoption; + + newoption = + init_ternary_reloption(RELOPT_KIND_LOCAL, name, desc, default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1626,6 +1705,19 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TERNARY: + { + bool b; + + parsed = parse_bool(value, &b); + option->values.ternary_val = b ? TERNARY_TRUE : TERNARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for ternary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1789,17 +1881,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1807,6 +1888,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TERNARY: + *(pg_ternary *) itempos = options[i].isset ? + options[i].values.ternary_val : + ((relopt_ternary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1923,8 +2009,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TERNARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -2004,7 +2090,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index aa4fbec143f..696eab9bd97 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2224,9 +2224,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TERNARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TERNARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 2f08e1b0cf0..dfbef2babf2 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TERNARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + pg_ternary ternary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_ternary +{ + relopt_gen gen; + int default_val; +} relopt_ternary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,9 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_ternary_reloption(bits32 kinds, const char *name, + const char *desc, pg_ternary default_val, + LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +212,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_ternary_reloption(local_relopts *relopts, + const char *name, const char *desc, + pg_ternary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/postgres.h b/src/include/postgres.h index 7d93fbce709..47c88780776 100644 --- a/src/include/postgres.h +++ b/src/include/postgres.h @@ -543,6 +543,21 @@ Float8GetDatum(float8 X) * ---------------------------------------------------------------- */ +/* + * pg_ternary + * Boolean value with an extra "unset" value + * + * This enum can be used for values that want to distinguish between true, + * false, and unset. +*/ + +typedef enum pg_ternary +{ + TERNARY_FALSE = 0, + TERNARY_TRUE = 1, + TERNARY_UNSET = -1 +} pg_ternary; + /* * NON_EXEC_STATIC: It's sometimes useful to define a variable or function * that is normally static but extern when using EXEC_BACKEND (see diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d03ab247788..5feb18a5373 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -347,8 +347,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + pg_ternary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index 9de19b4e3f1..1c99f79ab01 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -98,6 +98,42 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; {fillfactor=13,autovacuum_enabled=false} (1 row) +-- Tests for future (FIXME) ternary options +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=true} +(1 row) + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------ + {vacuum_truncate=fals} +(1 row) + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------- + {vacuum_index_cleanup=on} +(1 row) + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------- + {vacuum_index_cleanup=auto} +(1 row) + -- Test vacuum_truncate option DROP TABLE reloptions_test; CREATE TEMP TABLE reloptions_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 24fbe0b478d..f5980dafcbc 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -59,6 +59,27 @@ UPDATE pg_class ALTER TABLE reloptions_test RESET (illegal_option); SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +-- Tests for future (FIXME) ternary options + +-- behave as boolean option: accept unassigned name and truncated value +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_truncate=FaLS); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- preferred "true" alias is used when storing +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=on); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- custom "third" value is available +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (vacuum_index_cleanup=auto); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + -- Test vacuum_truncate option DROP TABLE reloptions_test; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3f3a888fd0e..1c8610fd46c 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3952,6 +3952,7 @@ pg_sha512_ctx pg_snapshot pg_special_case pg_stack_base_t +pg_ternary pg_time_t pg_time_usec_t pg_tz @@ -4079,6 +4080,7 @@ relopt_kind relopt_parse_elt relopt_real relopt_string +relopt_ternary relopt_type relopt_value relopts_validator -- 2.47.3 --c36t3fndxhkzbl5z-- ^ permalink raw reply [nested|flat] 853+ messages in thread
end of thread, other threads:[~2026-01-16 15:04 UTC | newest] Thread overview: 853+ messages (download: mbox mbox.gz follow: Atom feed) -- links below jump to the message on this page -- 2018-11-07 07:53 [PATCH 3/6] Make archiver process an auxiliary process Kyotaro Horiguchi <[email protected]> 2018-11-07 07:53 [PATCH v22 3/5] Make archiver process an auxiliary process Kyotaro Horiguchi <[email protected]> 2018-11-07 07:53 [PATCH 3/5] Make archiver process an auxiliary process Kyotaro Horiguchi <[email protected]> 2018-11-07 07:53 [PATCH 3/5] Make archiver process an auxiliary process Kyotaro Horiguchi <[email protected]> 2018-11-07 07:53 [PATCH 3/5] Make archiver process an auxiliary process Kyotaro Horiguchi <[email protected]> 2018-11-07 07:53 [PATCH 3/5] Make archiver process an auxiliary process Kyotaro Horiguchi <[email protected]> 2018-11-07 07:53 [PATCH 3/5] Make archiver process an auxiliary process Kyotaro Horiguchi <[email protected]> 2018-11-07 07:53 [PATCH 3/6] Make archiver process an auxiliary process Kyotaro Horiguchi <[email protected]> 2018-11-07 07:53 [PATCH 3/5] Make archiver process an auxiliary process Kyotaro Horiguchi <[email protected]> 2018-11-07 07:53 [PATCH 3/5] Make archiver process an auxiliary process Kyotaro Horiguchi <[email protected]> 2018-11-07 07:53 [PATCH v23 3/5] Make archiver process an auxiliary process Kyotaro Horiguchi <[email protected]> 2018-11-07 07:53 [PATCH 3/5] Make archiver process an auxiliary process Kyotaro Horiguchi <[email protected]> 2020-03-13 07:59 [PATCH v25 4/8] Make archiver process an auxiliary process Kyotaro Horiguchi <[email protected]> 2020-03-13 07:59 [PATCH v27 4/7] Make archiver process an auxiliary process Kyotaro Horiguchi <[email protected]> 2020-03-13 07:59 [PATCH v28 4/7] Make archiver process an auxiliary process Kyotaro Horiguchi <[email protected]> 2023-09-02 18:48 [PATCH 1/1] allow binaryheap to use any type Nathan Bossart <[email protected]> 2023-09-02 18:48 [PATCH 1/1] allow binaryheap to use any type Nathan Bossart <[email protected]> 2023-09-02 18:48 [PATCH 1/1] allow binaryheap to use any type Nathan Bossart <[email protected]> 2023-09-04 22:04 [PATCH v7 1/5] Allow binaryheap to use any type. Nathan Bossart <[email protected]> 2023-09-04 22:04 [PATCH v7 1/5] Allow binaryheap to use any type. Nathan Bossart <[email protected]> 2023-09-04 22:04 [PATCH v7 1/5] Allow binaryheap to use any type. Nathan Bossart <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2025-09-04 14:41 [PATCH v2a 2/4] Introduce ternary reloptions Nikolay Shaplov <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[email protected]> 2026-01-16 15:04 [PATCH v3] Introduce ternary reloptions Álvaro Herrera <[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